@trailofbits/vsix-audit 0.1.3 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (85) hide show
  1. package/README.md +9 -3
  2. package/dist/cli.d.ts.map +1 -1
  3. package/dist/cli.js +9 -242
  4. package/dist/cli.js.map +1 -1
  5. package/dist/formatters.d.ts +63 -0
  6. package/dist/formatters.d.ts.map +1 -0
  7. package/dist/formatters.js +268 -0
  8. package/dist/formatters.js.map +1 -0
  9. package/dist/scanner/bundler.d.ts +1 -2
  10. package/dist/scanner/bundler.d.ts.map +1 -1
  11. package/dist/scanner/bundler.js +12 -9
  12. package/dist/scanner/bundler.js.map +1 -1
  13. package/dist/scanner/cache.d.ts +10 -0
  14. package/dist/scanner/cache.d.ts.map +1 -1
  15. package/dist/scanner/cache.js +29 -1
  16. package/dist/scanner/cache.js.map +1 -1
  17. package/dist/scanner/capabilities.d.ts.map +1 -1
  18. package/dist/scanner/capabilities.js +26 -18
  19. package/dist/scanner/capabilities.js.map +1 -1
  20. package/dist/scanner/checks/ast.d.ts.map +1 -1
  21. package/dist/scanner/checks/ast.js +21 -41
  22. package/dist/scanner/checks/ast.js.map +1 -1
  23. package/dist/scanner/checks/ioc.d.ts +1 -0
  24. package/dist/scanner/checks/ioc.d.ts.map +1 -1
  25. package/dist/scanner/checks/ioc.js +60 -8
  26. package/dist/scanner/checks/ioc.js.map +1 -1
  27. package/dist/scanner/checks/ioc.test.js +175 -1
  28. package/dist/scanner/checks/ioc.test.js.map +1 -1
  29. package/dist/scanner/checks/obfuscation.d.ts.map +1 -1
  30. package/dist/scanner/checks/obfuscation.js +11 -12
  31. package/dist/scanner/checks/obfuscation.js.map +1 -1
  32. package/dist/scanner/checks/package.d.ts.map +1 -1
  33. package/dist/scanner/checks/package.js +15 -1
  34. package/dist/scanner/checks/package.js.map +1 -1
  35. package/dist/scanner/checks/package.test.js +5 -1
  36. package/dist/scanner/checks/package.test.js.map +1 -1
  37. package/dist/scanner/checks/telemetry.d.ts.map +1 -1
  38. package/dist/scanner/checks/telemetry.js +12 -29
  39. package/dist/scanner/checks/telemetry.js.map +1 -1
  40. package/dist/scanner/checks/telemetry.test.js +1 -0
  41. package/dist/scanner/checks/telemetry.test.js.map +1 -1
  42. package/dist/scanner/checks/yara.d.ts +2 -5
  43. package/dist/scanner/checks/yara.d.ts.map +1 -1
  44. package/dist/scanner/checks/yara.js +131 -68
  45. package/dist/scanner/checks/yara.js.map +1 -1
  46. package/dist/scanner/checks/yara.test.js +40 -10
  47. package/dist/scanner/checks/yara.test.js.map +1 -1
  48. package/dist/scanner/download.d.ts +0 -5
  49. package/dist/scanner/download.d.ts.map +1 -1
  50. package/dist/scanner/download.js +94 -86
  51. package/dist/scanner/download.js.map +1 -1
  52. package/dist/scanner/download.test.js +1 -16
  53. package/dist/scanner/download.test.js.map +1 -1
  54. package/dist/scanner/index.d.ts +4 -3
  55. package/dist/scanner/index.d.ts.map +1 -1
  56. package/dist/scanner/index.js +79 -58
  57. package/dist/scanner/index.js.map +1 -1
  58. package/dist/scanner/loaders/zoo.d.ts.map +1 -1
  59. package/dist/scanner/loaders/zoo.js +3 -1
  60. package/dist/scanner/loaders/zoo.js.map +1 -1
  61. package/dist/scanner/types.d.ts +39 -30
  62. package/dist/scanner/types.d.ts.map +1 -1
  63. package/dist/scanner/types.js +1 -1
  64. package/dist/scanner/types.js.map +1 -1
  65. package/dist/scanner/utils.d.ts +26 -4
  66. package/dist/scanner/utils.d.ts.map +1 -1
  67. package/dist/scanner/utils.js +59 -13
  68. package/dist/scanner/utils.js.map +1 -1
  69. package/dist/scanner/vsix.d.ts +6 -0
  70. package/dist/scanner/vsix.d.ts.map +1 -1
  71. package/dist/scanner/vsix.js +60 -24
  72. package/dist/scanner/vsix.js.map +1 -1
  73. package/dist/scanner/vsix.test.js +240 -3
  74. package/dist/scanner/vsix.test.js.map +1 -1
  75. package/package.json +1 -1
  76. package/zoo/blocklist/extensions.json +609 -3
  77. package/zoo/iocs/c2-domains.txt +10 -0
  78. package/zoo/iocs/c2-ips.txt +7 -0
  79. package/zoo/iocs/github-c2.txt +11 -0
  80. package/zoo/iocs/hashes.txt +6 -0
  81. package/zoo/iocs/wallets.txt +2 -5
  82. package/zoo/signatures/yara/blockchain_c2_extended.yar +57 -0
  83. package/zoo/signatures/yara/native_addon_loader.yar +71 -0
  84. package/zoo/signatures/yara/persistence_macos.yar +118 -0
  85. package/zoo/signatures/yara/rmm_tool_delivery.yar +106 -0
@@ -40,6 +40,12 @@ bb68992f6aa2d3f316322e88d9e71491a38e95fd3a27f48084c66b9c210bd17d # GlassWorm -
40
40
  68e5fb92a7d7d182306a025010c77ae2cd89c39031cced13f9686a9f34671041 # GlassWorm - f_ex86.node.decoded (Rust DLL, pre-decryption)
41
41
  a9a6a03bd6958710aeacc2a23860a7f7f0d09497fef85fe658ac5406734061f8 # GlassWorm - priskinski.theme-allhallowseve-remake-1.0.0.vsix (ReversingLabs Dec2025)
42
42
 
43
+ # SnowShoNo Campaign (ScreenConnect MSI)
44
+ 290027e4e32cf4983ccaa9811b3090c7397a3711d23e426ab144bec1167c456b # SnowShoNo - ScreenConnect MSI installer payload
45
+
46
+ # Pokemon/Cryptominer Campaign (Oct 2025)
47
+ f92824508a829846cebd427a073336be7b298b13691e28bde0e8c667fb0db436 # PokemonMiner - Monero miner executable (45/70 VT detections)
48
+
43
49
  # Zoo samples (GitHub sources)
44
50
  2cdaee2863396e558f17503ad290163d513acbc3c2ca2dbfa6852c2e064ca9f1 # SnowShoNo - PowerShell downloader (obfuscated)
45
51
  6674b3505ac23b2354230d691b9e18c2dda74a66f76bb0df724286cb9b23581c # ECM3401 - Extension Attack Suite .vsix
@@ -11,8 +11,5 @@
11
11
  # GlassWorm Campaign - Solana Blockchain C2
12
12
  SOL BjVeAjPrSKFiingBn4vZvghsGj9KCE8AJVtbc9S8o8SC # GlassWorm - Primary C2 wallet (transaction memos contain commands)
13
13
 
14
- # Add wallet addresses here as they are discovered
15
- # Format examples:
16
- # BTC 1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa # Campaign - Description
17
- # ETH 0x742d35Cc6634C0532925a3b844Bc9e7595f8fE42 # Campaign - Description
18
- # SOL ADDRESS # Campaign - Description
14
+ # SleepyDuck Campaign - Ethereum Smart Contract C2
15
+ ETH 0xDAfb81732db454DA238e9cFC9A9Fe5fb8e34c465 # SleepyDuck - Ethereum contract storing C2 server address
@@ -0,0 +1,57 @@
1
+ /*
2
+ Extended Blockchain C2 Detection
3
+ Detects Ethereum smart contract interaction patterns used
4
+ for command-and-control, extending the Solana-focused
5
+ blockchain_c2.yar rules.
6
+
7
+ SleepyDuck campaign uses Ethereum contracts to store C2
8
+ server addresses, queried via ethers.js/web3.js ABI calls.
9
+ The contract stores the current C2 URL which the malware
10
+ reads to get instructions, then executes the result.
11
+
12
+ Target: JavaScript files (no `wide` needed)
13
+ */
14
+
15
+ rule C2_JS_Ethereum_Contract_C2_Feb26 {
16
+ meta:
17
+ description = "Detects Ethereum contract queries combined with dynamic code execution (SleepyDuck C2 pattern)"
18
+ severity = "high"
19
+ score = 80
20
+ author = "vsix-audit"
21
+ date = "2026-02-06"
22
+ reference = "https://www.secureannex.com/blog/can-you-trust-your-vscode-extensions"
23
+
24
+ strings:
25
+ // Ethereum library imports
26
+ $lib1 = "ethers" ascii
27
+ $lib2 = "@ethersproject" ascii
28
+
29
+ // Contract interaction
30
+ $contract1 = "new Contract(" ascii
31
+ $contract2 = "getContract(" ascii
32
+
33
+ // RPC provider connection
34
+ $rpc1 = "JsonRpcProvider" ascii
35
+ $rpc2 = "InfuraProvider" ascii
36
+ $rpc3 = "AlchemyProvider" ascii
37
+ $rpc4 = "Web3Provider" ascii
38
+
39
+ // Dynamic code execution — the C2 signal
40
+ // child_process and new Function("return this") are too
41
+ // common in bundled Ethereum dev tools (webpack polyfills,
42
+ // compiler toolchains). Only eval() is a strong C2 signal
43
+ // when combined with contract interaction.
44
+ $exec1 = "eval(" ascii
45
+
46
+ condition:
47
+ any of ($lib*) and
48
+ any of ($contract*) and
49
+ any of ($rpc*) and
50
+ $exec1
51
+ }
52
+
53
+ // REMOVED: C2_JS_Blockchain_Address_Resolution_Feb26
54
+ // 87 FPs across 440-extension corpus. The getter patterns
55
+ // (.getServer, .getConfig, .getAddress) combined with
56
+ // Provider() + fetch() + child_process were far too generic —
57
+ // matched virtually any large bundled extension.
@@ -0,0 +1,71 @@
1
+ /*
2
+ Suspicious Native Addon Loading Detection
3
+ Detects platform-conditional loading of .node native addons,
4
+ a pattern used by GlassWorm Wave 3 to load Rust implants
5
+ (darwin.node, os.node) that establish persistence.
6
+
7
+ Legitimate native addons exist (e.g., node-sass, sharp) but
8
+ they load via npm packages, not inline require with
9
+ platform checks and custom-named .node files.
10
+
11
+ Target: JavaScript files (no `wide` needed)
12
+ */
13
+
14
+ rule SUSP_NativeAddon_Platform_Loader_Feb26 {
15
+ meta:
16
+ description = "Detects platform-conditional loading of .node native addons, a pattern used by GlassWorm for Rust implant delivery"
17
+ severity = "high"
18
+ score = 85
19
+ author = "vsix-audit"
20
+ date = "2026-02-06"
21
+ reference = "https://www.secureannex.com/blog/the-glass-is-half-empty"
22
+
23
+ strings:
24
+ // Platform detection
25
+ $platform1 = "os.platform()" ascii
26
+ $platform2 = "process.platform" ascii
27
+
28
+ // Platform value used in comparisons
29
+ // (must be full quoted strings to avoid short-atom issues)
30
+ $win = "'win32'" ascii
31
+ $mac = "'darwin'" ascii
32
+
33
+ // Native addon loading with relative path
34
+ $node_load1 = /require\s*\(\s*['"][^'"]*\.node['"]\s*\)/ ascii
35
+ $node_load2 = "darwin.node" ascii
36
+ $node_load3 = "os.node" ascii
37
+ $node_load4 = "win.node" ascii
38
+ $node_load5 = "linux.node" ascii
39
+
40
+ // The loaded addon is called with .run()
41
+ $run_call = ".run(" ascii
42
+
43
+ condition:
44
+ any of ($platform*) and
45
+ ($win or $mac) and
46
+ any of ($node_load*) and
47
+ $run_call
48
+ }
49
+
50
+ rule SUSP_NativeAddon_Bundled_Binary_Feb26 {
51
+ meta:
52
+ description = "Detects custom-named .node file loading outside of node_modules, suggesting a bundled native binary"
53
+ severity = "medium"
54
+ score = 70
55
+ author = "vsix-audit"
56
+ date = "2026-02-06"
57
+ reference = "https://www.secureannex.com/blog/the-glass-is-half-empty"
58
+
59
+ strings:
60
+ // Require of .node files with relative paths (not from node_modules)
61
+ $rel_node1 = /require\s*\(\s*['"]\.\/[^'"]+\.node['"]\s*\)/ ascii
62
+ $rel_node2 = /require\s*\(\s*['"]\.\.\/[^'"]+\.node['"]\s*\)/ ascii
63
+
64
+ // VS Code extension activation context
65
+ $activate = "activate" ascii
66
+ $vscode = "vscode" ascii
67
+
68
+ condition:
69
+ any of ($rel_node*) and
70
+ ($activate and $vscode)
71
+ }
@@ -0,0 +1,118 @@
1
+ /*
2
+ macOS Persistence Detection
3
+ Detects LaunchAgent/LaunchDaemon persistence mechanisms
4
+ used by malware to survive reboots on macOS.
5
+
6
+ GlassWorm Wave 3 Rust implants write LaunchAgent plists
7
+ to ~/Library/LaunchAgents/ with com.apple.* naming to
8
+ blend in with legitimate Apple services.
9
+
10
+ Target: JavaScript files (no `wide` needed)
11
+ */
12
+
13
+ rule SUSP_Mac_LaunchAgent_Feb26 {
14
+ meta:
15
+ description = "Detects LaunchAgent plist creation for macOS persistence, used by GlassWorm Rust implants"
16
+ severity = "high"
17
+ score = 85
18
+ author = "vsix-audit"
19
+ date = "2026-02-06"
20
+ reference = "https://www.secureannex.com/blog/the-glass-is-half-empty"
21
+
22
+ strings:
23
+ // LaunchAgent/LaunchDaemon paths
24
+ $la_path1 = "LaunchAgents" ascii
25
+ $la_path2 = "LaunchDaemons" ascii
26
+ $la_path3 = "Library/LaunchAgents" ascii
27
+ $la_path4 = "Library/LaunchDaemons" ascii
28
+
29
+ // Plist content patterns
30
+ $plist1 = "ProgramArguments" ascii
31
+ $plist2 = "RunAtLoad" ascii
32
+ $plist3 = "KeepAlive" ascii
33
+ $plist4 = "StartInterval" ascii
34
+
35
+ // File write operations
36
+ $write1 = "writeFile" ascii
37
+ $write2 = "writeFileSync" ascii
38
+ $write3 = "createWriteStream" ascii
39
+
40
+ // Or plist XML format
41
+ $xml1 = "<!DOCTYPE plist" ascii
42
+ $xml2 = "<plist version" ascii
43
+
44
+ condition:
45
+ any of ($la_path*) and
46
+ (2 of ($plist*) or any of ($xml*)) and
47
+ any of ($write*)
48
+ }
49
+
50
+ rule SUSP_Mac_AppleDisguise_Feb26 {
51
+ meta:
52
+ description = "Detects use of com.apple.* naming for non-Apple LaunchAgent persistence (masquerading)"
53
+ severity = "critical"
54
+ score = 90
55
+ author = "vsix-audit"
56
+ date = "2026-02-06"
57
+ reference = "https://www.secureannex.com/blog/the-glass-is-half-empty"
58
+
59
+ strings:
60
+ // Apple-disguised plist labels
61
+ $apple_label = /com\.apple\.[a-zA-Z0-9._-]+\.plist/ ascii
62
+
63
+ // LaunchAgent context
64
+ $la1 = "LaunchAgents" ascii
65
+ $la2 = "LaunchDaemons" ascii
66
+
67
+ // Non-Apple origin evidence (JS/Node context)
68
+ $js1 = "require(" ascii
69
+ $js2 = "child_process" ascii
70
+ $js3 = "execSync(" ascii
71
+ $js4 = "writeFile" ascii
72
+
73
+ condition:
74
+ $apple_label and
75
+ any of ($la*) and
76
+ any of ($js*)
77
+ }
78
+
79
+ rule SUSP_Mac_LoginItem_Feb26 {
80
+ meta:
81
+ description = "Detects programmatic addition of macOS Login Items for persistence"
82
+ severity = "medium"
83
+ score = 75
84
+ author = "vsix-audit"
85
+ date = "2026-02-06"
86
+ reference = "https://www.secureannex.com/blog/the-glass-is-half-empty"
87
+
88
+ strings:
89
+ // Login Items manipulation via osascript
90
+ $login1 = "login item" ascii nocase
91
+ $login2 = "LoginItems" ascii
92
+ $login3 = "LSSharedFileList" ascii
93
+
94
+ // osascript for AppleScript execution
95
+ $osa1 = "osascript" ascii
96
+ $osa2 = "tell application" ascii
97
+
98
+ // Open at Login
99
+ $open1 = "LSRegisterURL" ascii
100
+ $open2 = "open at login" ascii nocase
101
+
102
+ // Code execution context
103
+ $exec1 = "exec(" ascii
104
+ $exec2 = "execSync(" ascii
105
+ $exec3 = "child_process" ascii
106
+ $exec4 = "spawn(" ascii
107
+
108
+ // Exclude TextMate grammar/syntax files
109
+ // (contain AppleScript keywords as language tokens)
110
+ $fp_grammar1 = "tmLanguage" ascii
111
+ $fp_grammar2 = "scopeName" ascii
112
+ $fp_grammar3 = "repository" ascii
113
+
114
+ condition:
115
+ any of ($login*, $open*) and
116
+ (any of ($osa*) or any of ($exec*)) and
117
+ not 2 of ($fp_grammar*)
118
+ }
@@ -0,0 +1,106 @@
1
+ /*
2
+ RMM/RAT Tool Delivery Detection
3
+ Detects delivery of Remote Monitoring & Management tools
4
+ (ScreenConnect, AnyDesk, TeamViewer) via scripted installers.
5
+
6
+ Covers the "These Vibes Are Off" campaign pattern where
7
+ extensions use PowerShell IEX cradles to silently install
8
+ ScreenConnect for persistent remote access.
9
+
10
+ Target: JavaScript files in VS Code extensions (no `wide` needed)
11
+ */
12
+
13
+ rule LOADER_RMM_ScreenConnect_Delivery_Feb26 {
14
+ meta:
15
+ description = "Detects ScreenConnect (ConnectWise Control) delivery or silent installation patterns in extension code"
16
+ severity = "critical"
17
+ score = 95
18
+ author = "vsix-audit"
19
+ date = "2026-02-06"
20
+ reference = "https://www.secureannex.com/blog/these-vibes-are-off"
21
+
22
+ strings:
23
+ // ScreenConnect / ConnectWise Control identifiers
24
+ $sc1 = "screenconnect" ascii nocase
25
+ $sc2 = "ScreenConnect.Client" ascii
26
+ $sc3 = "ScreenConnect.WindowsClient" ascii
27
+ $sc4 = "connectwise" ascii nocase
28
+
29
+ // ScreenConnect relay/session URLs
30
+ $relay1 = /instance-[a-z0-9]+-relay\.screenconnect\.com/ ascii
31
+ $relay2 = /[a-z0-9]{4,30}\.screenconnect\.com/ ascii
32
+
33
+ // Script-based delivery context
34
+ $deliver1 = "child_process" ascii
35
+ $deliver2 = "exec(" ascii
36
+ $deliver3 = "execSync(" ascii
37
+ $deliver4 = "spawn(" ascii
38
+ $deliver5 = "powershell" ascii nocase
39
+
40
+ condition:
41
+ (any of ($sc*) or any of ($relay*)) and
42
+ any of ($deliver*)
43
+ }
44
+
45
+ rule LOADER_RMM_AnyDesk_Delivery_Feb26 {
46
+ meta:
47
+ description = "Detects AnyDesk silent installation or deployment patterns in extension code"
48
+ severity = "critical"
49
+ score = 90
50
+ author = "vsix-audit"
51
+ date = "2026-02-06"
52
+ reference = "https://www.secureannex.com/blog/these-vibes-are-off"
53
+
54
+ strings:
55
+ $ad1 = "anydesk" ascii nocase
56
+ $ad2 = "AnyDesk.exe" ascii
57
+
58
+ // AnyDesk silent install flags
59
+ $install1 = "--install" ascii
60
+ $install2 = "--silent" ascii
61
+ $install3 = "--start-with-win" ascii
62
+
63
+ // AnyDesk password setting (unattended access)
64
+ $pwd1 = "--set-password" ascii
65
+ $pwd2 = "ad.anynet.pwd" ascii
66
+
67
+ // Delivery context
68
+ $deliver1 = "child_process" ascii
69
+ $deliver2 = "exec(" ascii
70
+ $deliver3 = "powershell" ascii nocase
71
+ $deliver4 = "spawn(" ascii
72
+
73
+ condition:
74
+ any of ($ad*) and
75
+ (any of ($install*) or any of ($pwd*)) and
76
+ any of ($deliver*)
77
+ }
78
+
79
+ rule LOADER_RMM_TeamViewer_Delivery_Feb26 {
80
+ meta:
81
+ description = "Detects TeamViewer silent installation or deployment patterns in extension code"
82
+ severity = "critical"
83
+ score = 90
84
+ author = "vsix-audit"
85
+ date = "2026-02-06"
86
+ reference = "https://www.secureannex.com/blog/these-vibes-are-off"
87
+
88
+ strings:
89
+ $tv1 = "teamviewer" ascii nocase
90
+ $tv2 = "TeamViewer.exe" ascii
91
+ $tv3 = "TeamViewer_Setup" ascii
92
+
93
+ // Silent/unattended install
94
+ $install1 = "CUSTOMCONFIGID" ascii nocase
95
+ $install2 = "APITOKEN" ascii nocase
96
+
97
+ // Delivery context
98
+ $deliver1 = "child_process" ascii
99
+ $deliver2 = "exec(" ascii
100
+ $deliver3 = "powershell" ascii nocase
101
+ $deliver4 = "spawn(" ascii
102
+
103
+ condition:
104
+ any of ($tv*) and any of ($install*) and
105
+ any of ($deliver*)
106
+ }