{
	"id": "2ed0951e-5fbf-4009-9841-d87f6cf9b725",
	"created_at": "2026-04-06T00:20:06.408252Z",
	"updated_at": "2026-04-10T13:11:23.789056Z",
	"deleted_at": null,
	"sha1_hash": "fe9db69f0899b1032ad7368c34917b86ac88ec08",
	"title": "Analyzing the nasty .NET protection of the Ploutus.D malware.",
	"llm_title": "",
	"authors": "",
	"file_creation_date": "0001-01-01T00:00:00Z",
	"file_modification_date": "0001-01-01T00:00:00Z",
	"file_size": 329361,
	"plain_text": "Analyzing the nasty .NET protection of the Ploutus.D malware.\r\nArchived: 2026-04-05 14:30:35 UTC\r\nTwitter: @s4tan\r\nEDIT: The source code is now online: https://github.com/enkomio/Conferences/tree/master/HackInBo2018\r\nRecently the ATM malware Ploutus.D reappeared in the news as being used to attack US ATM ([1]). In this post\r\nI'll show a possible analysis approach aimed at understanding its main protection. The protection is composed of\r\ndifferent layers of protection, I'll focus on the one that, in my hopinion, is the most annoying, leaving the others\r\nout. If you want a clear picture of all the implied protections, I strongly recommend you to take a look at the\r\nde4dot Reactor deobfuscator code.\r\nIntroduction\r\nReversing .NET malware, in most cases, is not that difficult. This is mostly due to the awesome tool dnSpy ([2]),\r\nwhich allows debugging of the decompiled version of the Assembly. Most of the .NET malware use some kind of\r\nloader which decrypts a blob of data and then loads the result through a call to the Assembly.Load method ([3]).\r\nFrom time to time some more advanced protection are involved, like the one analysed by Talos in [4]. What the\r\narticle doesn't say is that in this specific case the malware uses a multi files assembly ([5]).\r\nThis implies that instead of using the Assembly.Load method, it uses the way less known Assembly.LoadModule\r\nmethod ([6]). This protection method is a bit more difficult to implement but I have to say that is way more\r\neffective as obfuscation. The malware also encrypt the method bodies and decrypt them only when necessary. This\r\nprotection is easily overcome by calling the \"Reload All Method Bodies\" command in dnSpy at the right moment\r\n(as also showed in the Talos article).\r\nPloutus.D is also protected with an obfuscator which encrypts the method bodies and decrypts them only when\r\nnecessary. The protector used is .NET Reactor ([7]) as also pointed out in a presentation by Karspersky ([8]). This\r\nparticular protection is called NecroBit Protection, and from the product website we can read that:\r\nNecroBit is a powerful protection technology which stops decompilation. NecroBit replaces the CIL\r\ncode within methods with encrypted code. This way it is not possible to decompile/reverse engineer\r\nyour method source code.\r\nThe difference with the previous case is that if we try to use the \"Reload All Method Bodies\" feature in dnSpy, it\r\nwill fail (this is not technically correct since there is nothing to reload as we will see).\r\nReversing Ploutus.D obfuscation\r\nTo write this blog post I have reversed the sample with MD5 ae3adcc482edc3e0579e152038c3844e. When I start\r\nto analyse a .NET malware, as first task I ran my tool Shed ([9]) in order to have a broad overview of what the\r\nhttp://antonioparata.blogspot.co.uk/2018/02/analyzing-nasty-net-protection-of.html\r\nPage 1 of 11\n\nmalware does and to try to extract dynamically loaded Assemblies. In this case I was able to extract some useful\r\nstrings (like the configured backend usbtest[.]ddns[.]net) but not the Assembly with the method bodies decrypted\r\n(however this is not an error and as we will see it is the correct behaviour).\r\nThe next step is to debug the program with dnSpy. If you run it the following Form will be displayed:\r\nI started to dig a bit on the classes that extend the Form class in order to identify which commands are supported.\r\nUnfortunately most of the methods of these classes are empty, as can be seen from the following screenshot:\r\nIt is interesting to note that all the static constructors are not empty. All of them are pretty simple (in some cases\r\nthey have just one instruction), what it is interesting is that all of them call the same method:\r\nP9ZBIKXMsRMxLdTfcG.Nf9E3QXmJD();, which is marked as internal unsafe static void Nf9E3QXmJD().\r\nBy analysing it, the thing start to get interesting since this method is pretty huge, especially since it implements a\r\nvery annoying control flow obfuscation. It is interesting to notice that if we set a breakpoint on this method and re-http://antonioparata.blogspot.co.uk/2018/02/analyzing-nasty-net-protection-of.html\r\nPage 2 of 11\n\nstart the debugging session, it is amongst the first methods invoked by the program. Scrolling through the code we\r\ncan find the following interesting statement:\r\nif (P9ZBIKXMsRMxLdTfcG.Ax6OYTY7tiMf4Yu1B4(P9ZBIKXMsRMxLdTfcG.XnSi7dQe0TUTJbDcxg(P9ZBIKXMsRMxLdTfcG.CQ\r\nThis piece of code is particularly interesting, since it tries to identify the clrjit.dll module. Once found, it identifies\r\nthe CLR version, which in my case is 4.0.30319.0. Then, it extracts the resource\r\nm7fEJg2w6sBe9LM3D3.i4tjc9Xt0Vhu5G72Uh.\r\nAfter a while the getJit string appears in the execution. This function is exported by clrjit.dll and it is a very\r\nimportant method since it allows to get a pointer to the compileMethod method. To know more about it you could\r\nrefer to my Phrack article about .NET program instrumentation ([10]). We can also identify a call to the\r\nVirtualProtect method.\r\nWith these information we can start to make some assumption, like that the malware hook the compileMethod\r\nmethod in order to force the compilation of the real MSIL bytecode. Let's verify our assumption, in order to do so\r\nwe need to change tool, in particular we will use WinDbg with the SOS extension (if you want to know more about\r\ndebugging .NET applications with WinDbg take a look at my presentaion [11]).\r\nIn order to inspect the program at the right moment, we will set an exception when the clrjit.dll library is loaded.\r\nThis is easily done with the command:\r\nsxe ld clrjit.dll\r\nonce that this exception is raised let's inspect the clrjit module as showed in the following image:\r\nThe getJit method is an exported by clrjit dll and returns the address of the VTable of an ICorJitCompiler object,\r\nwhere the first item is a pointer to the compileMethod method, as can be seen from the source code ([12]). But,\r\nsince we don't trust the source code, let's debug the getJit method till the ret instruction and inspect the return\r\nvalue stored in eax:\r\nhttp://antonioparata.blogspot.co.uk/2018/02/analyzing-nasty-net-protection-of.html\r\nPage 3 of 11\n\nas can be seen from the image above, the address of the compileMethod is at 0x70f049b0. Now let's the program\r\nrun until the main windows is displayed and then break the process in the debugger. Let's display again the content\r\nof the VTable (which was 0x70f71420).\r\nAs can be seen from the image above the value of the first entry of the VTable changed to from 0x70f049b0 to\r\n002a0000. So our assumption about the hooking of the compileMethod was right :)\r\nNow we want to identify which method hooked the compileMethod method. To do this we will load the SOS\r\nextension (with the command .loadby SOS clrjit), set a breakpoint at the compileMethod method and when the\r\nbrakpoint hits, type !CLRStack command to see which method was set as replacement. In order to trigger the\r\ncompileMethod breakpoint I clicked on a random button in the interface.\r\nfrom the image above we can spot that the interested method is qtlEIBBYuV. Find below the decompiled code of\r\nthe metohd (I have renamed the argument names and added some comments):\r\n[MethodImpl(MethodImplOptions.NoInlining)]\r\ninternal static uint qtlEIBBYuV(IntPtr \\u0020, IntPtr a1, IntPtr \\u0020, [MarshalAs(UnmanagedType.U4)]\r\nuint \\u0020, IntPtr \\u0020, ref uint \\u0020)\r\n{\r\n// thisi s a pointer to the COREINFO_METHOD_INFO structure\r\nIntPtr ptr = \\u0020;\r\nif (P9ZBIKXMsRMxLdTfcG.KOnEge1tX2)\r\nhttp://antonioparata.blogspot.co.uk/2018/02/analyzing-nasty-net-protection-of.html\r\nPage 4 of 11\n\n{\r\nptr = a1;\r\n}\r\nlong num;\r\nif (IntPtr.Size == 4)\r\n{\r\nnum = (long)Marshal.ReadInt32(ptr, IntPtr.Size * 2); (1)\r\n}\r\nelse\r\n{\r\nnum = Marshal.ReadInt64(ptr, IntPtr.Size * 2);\r\n}\r\n// load the ILCode address\r\nobject obj = P9ZBIKXMsRMxLdTfcG.k6dbsY0qhy[num];\r\nif (obj == null)\r\n{\r\n// proceed with the standard compilation of the method\r\nreturn P9ZBIKXMsRMxLdTfcG.u8lbx6nt2g(\\u0020, a1, \\u0020, \\u0020, \\u0020, ref \\u0020);\r\n}\r\n// allocate a pinned memory buffer in order to copy the real IL code\r\nP9ZBIKXMsRMxLdTfcG.QjP40LkcxwMvQVmG1a qjP40LkcxwMvQVmG1a =\r\n(P9ZBIKXMsRMxLdTfcG.QjP40LkcxwMvQVmG1a)obj; (2)\r\nIntPtr intPtr = Marshal.AllocCoTaskMem(qjP40LkcxwMvQVmG1a.wsQbTfcGfS.Length);\r\nMarshal.Copy(qjP40LkcxwMvQVmG1a.wsQbTfcGfS, 0, intPtr,\r\nqjP40LkcxwMvQVmG1a.wsQbTfcGfS.Length);\r\nif (qjP40LkcxwMvQVmG1a.KJXb28UyS5)\r\n{\r\nhttp://antonioparata.blogspot.co.uk/2018/02/analyzing-nasty-net-protection-of.html\r\nPage 5 of 11\n\n// call VirtualProtect if necessary\r\na1 = intPtr;\r\na1 = (uint)qjP40LkcxwMvQVmG1a.wsQbTfcGfS.Length;\r\nP9ZBIKXMsRMxLdTfcG.fptEBhe4Kh(\\u0020, qjP40LkcxwMvQVmG1a.wsQbTfcGfS.Length, 64, ref\r\nP9ZBIKXMsRMxLdTfcG.Tufbd24KkS);\r\nreturn 0u;\r\n}\r\n// write back the address of the COREINFO_METHOD_INFO.ILCode\r\nMarshal.WriteIntPtr(ptr, IntPtr.Size * 2, intPtr); (3)\r\n// write back the real length, field COREINFO_METHOD_INFO.ILCodeSize\r\nMarshal.WriteInt32(ptr, IntPtr.Size * 3, qjP40LkcxwMvQVmG1a.wsQbTfcGfS.Length); (4)\r\nuint result = 0u;\r\nif (\\u0020 != 216669565u || P9ZBIKXMsRMxLdTfcG.iJnbQwwaCg)\r\n{\r\n// call the real compileMethod with the real ILcode\r\nresult = P9ZBIKXMsRMxLdTfcG.u8lbx6nt2g(\\u0020, a1, \\u0020, \\u0020, \\u0020, ref \\u0020); (5)\r\n}\r\nelse\r\n{\r\nP9ZBIKXMsRMxLdTfcG.iJnbQwwaCg = true;\r\n}\r\nreturn result;\r\n}\r\nWhat is interesting from the code above is that:\r\nit reads the address of the COREINFO_METHOD_INFO structure at (1)\r\nwrites back the real MSIL bytecode at (2)\r\nupdates the fields ILCode and ILCodeSize at (3) and (4)\r\nfinally call the original compileMethod at (5)\r\nhttp://antonioparata.blogspot.co.uk/2018/02/analyzing-nasty-net-protection-of.html\r\nPage 6 of 11\n\nIn this way, it is sure that the correct MSIL code is compiled and executed (for more info on this structure please\r\nrefer to [10,12]).\r\nFinally, we have a pretty good understanding of how the real code is protected, now we can try to implement a\r\nsimple program which dumps the real MSIL bytecode and rebuilds the assembly. The de4dot tool, instead, uses a\r\ndifferent approach, which is based on emulating the decryption code of the method body and then rebuild the\r\nassembly.\r\nLet's the code speak\r\nA possible approach to dump the real MSIL bytecode is:\r\nHook the compileMethod before the malware\r\nForce all static constructors to be invoked and force compilation of all methods via\r\nRuntimeHelpers.PrepareMethod. This will ensure that we are able to grab all the ILCode of the various\r\nmethods.\r\nWhen the hook is invoked store the values of the fields ILCode and ILCodeSize. We have to record also\r\nwhich method is currently compiled, this is done with the code getMethodInfoFromModule from [10].\r\nRebuild the assembly by using Mono.Cecil or dnlib (my choice)\r\nHowever, for this specific case, I'll use a slightly different approach, which is not as generic as the previous one\r\nbut it is simpler and more interesting imho :)\r\nAs we have seen from the code above, the P9ZBIKXMsRMxLdTfcG.k6dbsY0qhy is a dictionary of objects which\r\ncontains the real MSIL bytecode as value and as key the address of the MSIL buffer. What we can do is to read the\r\nvalue of this object via reflection and rebuild the original binary. All this without implying the hooking of any\r\nmethods :)\r\nI have implemented a simple program that extracts those values via reflection, calculates the address of each\r\nmethod and rebuild the assembly. If you want to take a look it, here is the code.\r\nopen System\r\nopen System.Linq\r\nopen System.Reflection\r\nopen System.Runtime.CompilerServices\r\nopen System.Collections\r\nopen System.Collections.Generic\r\nopen System.Diagnostics\r\nopen Microsoft.Diagnostics.Runtime\r\nopen dnlib.DotNet\r\nhttp://antonioparata.blogspot.co.uk/2018/02/analyzing-nasty-net-protection-of.html\r\nPage 7 of 11\n\nopen dnlib.DotNet.Emit\r\nopen dnlib.IO\r\nlet runAllStaticConstructor(assembly: Assembly) =\r\nassembly.GetTypes()\r\n|\u003e Seq.iter(fun assemblyType -\u003e\r\nRuntimeHelpers.RunClassConstructor(assemblyType.TypeHandle)\r\n)\r\nlet getMsilStorage(assembly: Assembly) =\r\nlet assemblyType = assembly.GetType(\"yasttpQOrHx2jEkiUL.P9ZBIKXMsRMxLdTfcG\")\r\nlet storageField = assemblyType.GetField(\"k6dbsY0qhy\", BindingFlags.NonPublic ||| BindingFlags.Static)\r\nlet hashTable = storageField.GetValue(null) :?\u003e Hashtable\r\nhashTable.Cast\u003cDictionaryEntry\u003e()\r\n|\u003e Seq.map(fun kv -\u003e\r\n// extract bytes array\r\nlet bytesField =\r\nkv.Value.GetType().GetFields(BindingFlags.Instance ||| BindingFlags.NonPublic)\r\n|\u003e Seq.find(fun field -\u003e field.FieldType.IsArray)\r\nlet msilBytes = bytesField.GetValue(kv.Value) :?\u003e Byte array\r\n(kv.Key :?\u003e Int64, msilBytes))\r\n|\u003e Map.ofSeq\r\nlet getAssemblyBaseAddress(assembly: Assembly) =\r\nlet pid = Process.GetCurrentProcess().Id\r\nlet dataTarget = DataTarget.AttachToProcess(pid, uint32 5000, AttachFlag.Passive)\r\nlet runtime = dataTarget.ClrVersions.[0].CreateRuntime()\r\nlet assemblyModule = runtime.Modules |\u003e Seq.find(fun m -\u003e m.Name.Equals(assembly.Location))\r\nhttp://antonioparata.blogspot.co.uk/2018/02/analyzing-nasty-net-protection-of.html\r\nPage 8 of 11\n\nlet baseAddress = int32 assemblyModule.ImageBase\r\ndataTarget.Dispose()\r\nbaseAddress\r\n[\u003cEntryPoint\u003e]\r\nlet main argv =\r\nlet fullPath =\r\n@\"PloutusD_d93342bd12ef44d92bf58ed2f0f88443385a0192804a5d0976352484c0d37685.exe\"\r\nlet assembly = Assembly.LoadFile(fullPath)\r\n// run all static constructor to fill the protection dictionary containing the real MSIL bytecode\r\nrunAllStaticConstructor(assembly)\r\n// retrieve the dictionary via reflection. This is higly dependant to a specific sample, the field name may\r\nchange from sample to sample\r\nlet msilStorage = getMsilStorage(assembly)\r\n// get the malware base addres in order to calculate the method body address\r\nlet baseAddress = getAssemblyBaseAddress(assembly)\r\n// use a modified version of dnlib in order to set the real MSIL bytecode\r\nlet dnModule = ModuleDefMD.Load(fullPath)\r\ndnModule.Types\r\n|\u003e Seq.map(fun t -\u003e t.Methods)\r\n|\u003e Seq.concat\r\n|\u003e Seq.filter(fun dnMethod -\u003e dnMethod.HasBody)\r\n|\u003e Seq.iter(fun dnMethod -\u003e\r\ndnMethod.Body.KeepOldMaxStack \u003c- true\r\n// skip the body header\r\nlet offset = int32 dnMethod.Body.HeaderSize\r\nhttp://antonioparata.blogspot.co.uk/2018/02/analyzing-nasty-net-protection-of.html\r\nPage 9 of 11\n\nlet ilAddress = int32 dnMethod.RVA + baseAddress + offset\r\nif msilStorage.ContainsKey(int64 ilAddress) then\r\nlet realMsilBytes = msilStorage.[int64 ilAddress]\r\ndnMethod.Body.MaxStack \u003c- uint16 50\r\n// set the body via raw buffer. This property is not present in the real dnlib code :P\r\ndnMethod.Body.RawBody \u003c- new List\u003cByte\u003e(realMsilBytes)\r\nConsole.WriteLine(\"Rebuit: \" + dnMethod.FullName)\r\n)\r\n// finally write back the new assembly\r\ndnModule.Write(fullPath + \"_rebuilt\")\r\n0\r\nAfter dumped the real MSIL, we can see that now the methods are not empty anymore:\r\nConclusion\r\nThe purpose of this post was to show how to analyse, in an effective way, a strongly obfuscate malware with the\r\nhelp of different tools and the knowledge of the internal working of the .NET framework.\r\nAs an alternative, if you want to obtain a de-obfuscated sample I encourage you to use the de4dot tool (and to read\r\nthe code since this project is a gold mine of information related to the .NET internals).\r\nAt the time of this writing the sample is not correctly deobfuscated by de4dot due to an error in the string\r\ndecryption step. To obtain a deobfuscated sample with the real method body, just comment out the string\r\ndecryption step in ObfuscatedFile.cs.\r\nToo often developers underestimate the power of reflection and as a result it is not uncommon to bypass protection\r\n(included license verification code) only by using reflection and nothing more :)\r\nhttp://antonioparata.blogspot.co.uk/2018/02/analyzing-nasty-net-protection-of.html\r\nPage 10 of 11\n\nReferences\r\n[1] First ‘Jackpotting’ Attacks Hit U.S. ATMs - https://goo.gl/6WY14V\r\n[2] dnSpy - https://github.com/0xd4d/dnSpy\r\n[3] Assembly.Load Method (Byte[]) - https://goo.gl/owZtC1\r\n[4] Recam Redux - DeConfusing ConfuserEx - https://goo.gl/oKgj1k\r\n[5] How to: Build a Multifile Assembly - https://goo.gl/mVdHuU\r\n[6] Assembly.LoadModule Method (String, Byte[]) - https://goo.gl/D6N797\r\n[7] .NET REACTOR - http://www.eziriz.com/dotnet_reactor.htm\r\n[8] Threat hunting .NET malware with YARA.pdf - https://goo.gl/RxEw1G\r\n[9] Shed, .NET runtime inspector - https://github.com/enkomio/shed\r\n[10] http://www.phrack.org/papers/dotnet_instrumentation.html\r\n[11] .NET for hackers - https://www.slideshare.net/s4tan/net-for-hackers\r\n[12] getJit() - https://github.com/dotnet/coreclr/blob/master/src/inc/corjit.h#L241\r\nSource: http://antonioparata.blogspot.co.uk/2018/02/analyzing-nasty-net-protection-of.html\r\nhttp://antonioparata.blogspot.co.uk/2018/02/analyzing-nasty-net-protection-of.html\r\nPage 11 of 11",
	"extraction_quality": 1,
	"language": "EN",
	"sources": [
		"ETDA",
		"Malpedia"
	],
	"origins": [
		"web"
	],
	"references": [
		"http://antonioparata.blogspot.co.uk/2018/02/analyzing-nasty-net-protection-of.html"
	],
	"report_names": [
		"analyzing-nasty-net-protection-of.html"
	],
	"threat_actors": [],
	"ts_created_at": 1775434806,
	"ts_updated_at": 1775826683,
	"ts_creation_date": 0,
	"ts_modification_date": 0,
	"files": {
		"pdf": "https://archive.orkl.eu/fe9db69f0899b1032ad7368c34917b86ac88ec08.pdf",
		"text": "https://archive.orkl.eu/fe9db69f0899b1032ad7368c34917b86ac88ec08.txt",
		"img": "https://archive.orkl.eu/fe9db69f0899b1032ad7368c34917b86ac88ec08.jpg"
	}
}