ClickySkip to main content
Nu hulp nodig bij een cyberincident?
Bel 24/7: 088-2747800

Bug veroorzaakt problemen bij grote bestanden

Door 7 november 2019 april 9th, 2023 Blog

In de afgelopen weken hebben de onderzoekers van Tesorion gewerkt aan gratis decryptie-tools voor de Nemty-ransomware (zie ook onze vorige blogs Een decryptor voor de Nemty-ransomware gebaseerd op een analyse van de cryptografie and Update over Nemty: decryptors voor Nemty 1.5 en 1.6). Ons CSIRT-team heeft inmiddels een groot aantal Nemty-slachtoffers geholpen om hun bestanden kosteloos terug te halen met behulp van deze tools.

Tijdens het helpen van de slachtoffers, kwamen medewerkers van het Tesorion CSIRT-team erachter dat ze helaas in sommige gevallen niet in staat bleken om bestanden te ontsleutelen waarvan we dachten dat onze decryptoren ze goed zouden moeten kunnen verwerken. Na bestudering van enkele van de problematische versleutelde bestanden en een zorgvuldige extra controle van de Nemty-codering, constateerden we dat zeer grote bestanden (van meerdere gigabytes) niet correct konden worden ontsleuteld door onze tools. Helaas werd dit niet veroorzaakt door een bug in onze tools, maar door een bug in de Nemty-ransomware bestandscodering. We concludeerden dat het Nemty-encryptieproces onomkeerbaar is voor deze bestanden. Deze onomkeerbaarheid komt voort uit het feit dat de ransomware een deel van het bestand overschrijft met de versleutelde data van een ander deel. Er is dus geen manier om de overschreven data te herstellen. Niet door ons, en zelfs niet door een decryptor die door de Nemty-ontwikkelaars zelf zou zijn ontwikkeld.

In het kort

Betaling van het losgeld wordt nooit aanbevolen, omdat er geen garantie is dat de auteurs van de malware u na betaling een decryptor zullen leveren. Bovendien wordt door het betalen van het losgeld het ransomware-bedrijfsmodel versterkt en zullen cybercriminelen hoogstwaarschijnlijk doorgaan met hun schadelijke activiteiten.

In deze blogpost beschreven we een bug in de Nemty-ransomware encryptiecode die grote bestanden corrumpeert, waardoor correcte ontsleuteling onmogelijk is. Dit toont aan dat zelfs als een slachtoffer het losgeld zou betalen aan de Nemty-auteurs (wat in de meeste gevallen onnodig zou moeten zijn vanwege onze gratis decryptie-tools voor Nemty-ransomware tot aan versie 1.6), er nog steeds geen enkele manier is om dergelijke beschadigde grote bestanden volledig te herstellen; een deel van de gegevens wordt simpelweg vernietigd door de Nemty-ransomware in plaats van op een omkeerbare manier versleuteld.

Het slechte nieuws is dus: als je slachtoffer bent geworden van Nemty en je hebt grote gecodeerde bestanden (2147586048 bytes of groter, dus iets meer dan 2GB), is het helaas niet waarschijnlijk dat je alle gegevens in die grote bestanden kunt herstellen. Door de bug in de Nemty-encryptiecode die ervoor zorgt dat een deel van de gegevens wordt vernietigd, kunnen wij of iemand anders, met inbegrip van de Nemty-auteurs, daar simpelweg niets aan doen.

Gedeeltelijke bestandsversleuteling

Het lezen van een bestand, het versleutelen ervan en het vervolgens weer terugschrijven kost tijd. Sommige auteurs van ransomware kiezen er daarom voor om bij grote bestanden niet het hele bestand te versleutelen, maar alleen een of meer kleinere delen ervan. Daarmee wordt het bestand nog steeds onbruikbaar voor het slachtoffer, maar het proces bespaart aanzienlijk veel tijd in de encryptie van grote bestanden.

Nemty-versies tot en met 1.5 waren gebaseerd op een aangepaste (en gebugde) implementatie van AES-256 (zie ook onze eerste blog over Nemty). De bestandsversleuteling met behulp van deze implementatie was relatief langzaam. De ontwikkelaars kwamen waarschijnlijk al vroeg tot dezelfde conclusie en besloten om alleen delen van grotere bestanden te versleutelen, in plaats van de complete bestanden. Er zitten een aantal kleine verschillen tussen oudere en nieuwere Nemty-versies met betrekking tot welke delen van een bestand versleuteld worden. In dit artikel beschrijven we dit alleen voor Nemty 1.6, maar oudere versies werken min of meer hetzelfde (en bevatten dezelfde bug).

Als de malware in Nemty 1.6 een bestand begint te versleutelen, bepaalt het eerst de oorspronkelijke bestandsgrootte. Als het bestand niet groter is dan 6400000 bytes (iets meer dan 6 MB), wordt alleen het eerste deel van het bestand tot 102400 bytes versleuteld. De grootte van het versleutelde deel wordt naar beneden afgerond op blokken van 16 bytes (128 bits, de AES-blokgrootte). Dit deel van het bestand wordt overschreven met de versleutelde data en de rest van het bestand blijft onversleuteld. Er wordt een Nemty-specifieke footer toegevoegd aan het bestand, met wat base64-geëncodeerde, RSA-versleutelde data die nodig is voor de decodering, en een 15-byte “tag” met de string ‘_NEMTY_’, 7 willekeurige tekens die specifiek zijn voor de infectie, en een laatste ‘_’.

Als het oorspronkelijke bestand groter is dan 6400000 bytes, worden drie delen van het bestand versleuteld (in deze volgorde): een deel van 1600000 bytes net voor het midden van het bestand, de eerste 102400 bytes en de laatste 102400 bytes. De originele data wordt weer overschreven met de versleutelde data. De rest van het bestand wordt niet versleuteld. Ook hier wordt de Nemty-specifieke footer toegevoegd aan het bestand.

Deze verschillende gevallen van gedeeltelijke bestandsversleuteling worden in de onderstaande afbeelding weergegeven (niet op schaal):

Het probleem met grote bestanden

De gedeeltelijke bestandsversleuteling zoals beschreven in de vorige paragraaf is niet problematisch voor ontcijfering: met de sleutel en IV kunnen we de versleutelde delen eenvoudigweg ontsleutelen en de reeds onversleutelde gegevens intact laten. Het encryptieproces is in principe omkeerbaar, zoals onze decryptor ook laat zien.

Helaas bevat de Nemty-encryptiecode een ernstige bug die een correcte ontcijfering van bestanden groter dan iets meer dan 2GB onmogelijk maakt. Om deze bug te begrijpen, moeten we eerst wat meer inzicht krijgen in de Windows API: de functies die door Windows programma’s worden gebruikt om bestanden te lezen en te schrijven. Deze API is gebaseerd op het principe van een bestandsaanwijzer: de locatie in het bestand waar de volgende lees- of schrijfoperatie zal plaatsvinden. De bestandsaanwijzer wordt automatisch aan het einde van een lees- of schrijfoperatie ingesteld, maar hij kan ook expliciet worden verplaatst, bijvoorbeeld om over een deel van het bestand te schrijven dat net is gelezen. De belangrijkste functies die worden gebruikt om bestanden te manipuleren in het Nemty-coderingsproces, zijn de volgende:

  • GetFileSizeEx: deze functie bepaalt de grootte van een bestand
  • SetFilePointer: deze functie herpositioneert de bestandsaanwijzer voor de volgende lees- of schrijfoperatie op een bestand
  • ReadFile / WriteFile: deze functies voeren lees- of schrijfbewerkingen uit bij de huidige bestandsaanwijzer

Aan het begin van het encryptieproces voor een bestand wordt de functie GetFileSizeEx gebruikt om de oorspronkelijke bestandsgrootte te bepalen. De grootte die deze functie teruggeeft is een 64-bit signed integer (een 64-bit signed integer waarde kan het correcte aantal bytes weergeven in bestanden groter dan een Exabyte). Deze waarde wordt vervolgens gebruikt in berekeningen zoals de vergelijking om te bepalen of de bestandsgrootte meer dan 6400000 bytes is, en de berekeningen om het midden van het bestand te bepalen. Voor elk deel van het bestand dat versleuteld moet worden, wordt de bestandsaanwijzer verplaatst naar het begin van dat deel met behulp van SetFilePointer, de originele gegevens worden gelezen met ReadFile (waarbij de bestandsaanwijzer ook impliciet vooruit wordt geplaatst), de bestandsaanwijzer wordt dan terug verplaatst naar het begin van het deel met behulp van SetFilePointer, en de versleutelde gegevens worden teruggeschreven met WriteFile (waarbij de bestandsaanwijzer ook weer vooruit wordt verplaatst).

De SetFilePointer-functie die wordt gebruikt om de bestandsaanwijzer te verplaatsen, pakt niet simpelweg een enkele 64-bits waarde als offset. In plaats daarvan zijn er twee afzonderlijke argumenten nodig: lpDistanceToMoveHigh, een wijzer naar de hoogste 32 bits van een signed 64-bits offset die ook NULL kan zijn om 32-bits operatie aan te duiden, en lDistanceToMove, de laagste 32 bits van de signed offset. Als lpDistanceToMoveHigh NULL is, wordt alleen de waarde van lDistanceToMove gebruikt, en wordt deze geïnterpreteerd als een signed 32-bits waarde. Als de aangeroepen offset niet binnen een 32-bits signed waarde past, moet deze worden aangeduid als een 64-bits signed waarde die wordt verdeeld over lDistanceToMove en de waarde die wordt aangewezen door lpDistanceToMoveHigh.

Helaas hebben de Nemty-auteurs hier geen rekening mee gehouden bij het schrijven van hun software: in hun aanroep van SetFilePointer is lpDistanceToMoveHigh altijd ingesteld op NULL, dus de waarde van lDistanceToMove wordt altijd ingezet als een signed 32-bits waarde. Voor bestanden onder 2GB werkt dit prima: de hoogste 32 bits van alle relevante waarden zijn altijd gelijk aan 0 en de werkelijke positieve offset vanaf het begin van het bestand past binnen de 32-bits signed waarde, waardoor de code werkt zoals bedoeld. Bij bestanden van 2GB of groter lopen we echter tegen een probleem aan: waarden van 2GB en groter kunnen niet worden gerepresenteerd in een 32-bits signed waarde, en door simpelweg de laagste 32 bits van de oorspronkelijke 64-bits waarde te interpreteren, wordt de bestandsaanwijzer-offset soms als een negatieve waarde geïnterpreteerd.

Als de geherinterpreteerde waarde negatief is, wordt de bestandsaanwijzer in het bestand naar achteren verplaatst. En aangezien alle bewegingen in de Nemty-code relatief zijn ten opzichte van het begin van het bestand, zijn alle negatieve offsets ongeldig. Als SetFilePointer er niet in slaagt de bestandsaanwijzer te herpositioneren, bijvoorbeeld omdat de nieuwe offset buiten de grenzen van het bestand ligt, blijft de bestandsaanwijzer op zijn oude locatie staan. In de volgende paragraaf laten we aan de hand van een voorbeeld zien hoe dit ervoor zorgt dat bestanden onherstelbaar beschadigd raken tijdens het encryptieproces.

De gemakkelijkste oplossing voor dit probleem zou zijn om SetFilePointerEx te gebruiken in plaats van SetFilePointer.

We hebben intern overlegd of we deze oplossing wilden beschrijven, omdat we eigenlijk auteurs van malware niet willen helpen. Maar in dit geval hebben we liever dat de bestanden van een slachtoffer op een omkeerbare manier worden versleuteld, dan dat ze worden versleuteld en corrupt raken. Dus, Nemty-auteurs, als jullie onze blogs nog steeds lezen, los dit alsjeblieft op, zodat we allebei de oorspronkelijke data van de slachtoffers terug kunnen halen, in plaats van geen van beide.

Een voorbeeld van bestandscorruptie

Om te illustreren hoe dit een bug in de Nemty-encryptiecode veroorzaakt, nemen we een bestand van 2147586048 bytes. De offsets voor het middendeel en het begin van dit bestand geven geen problemen: ze worden berekend aan de hand van 64-bits waarden en de resultaten passen mooi in een 32-bits signed waarde, dus hier geen problemen. Vervolgens wordt de bestandsaanwijzer verplaatst naar de eerste offset in het bestand en worden de eerste 102400 bytes versleuteld zoals bedoeld.

Wanneer echter de offset van de laatste 102400 bytes van het bestand wordt berekend, is het resultaat 2147586048 – 102400 = 2147483648. Deze waarde is 0x80000000 in hexadecimaal en kan niet correct worden weergegeven met behulp van een 32-bits signed waarde. Als deze waarde wordt geïnterpreteerd als een signed 32-bits integer, krijgt het de waarde -2147483648. SetFilePointer probeert dan de bestandsaanwijzer te verplaatsen naar min 2147483648.

Deze operatie mislukt echter, omdat dit geen geldige positie is. De Nemty-code controleert niet op foutmeldingen betreffende het aanroepen van SetFilePointer en leest vervolgens 102400 bytes van het bestand. Deze leesoperatie verplaatst de bestandsaanwijzer impliciet 102400 bytes vooruit. De ransomware versleutelt deze bytes en roept vervolgens SetFilePointer opnieuw aan met de waarde -2147483648 om terug te gaan naar het begin van de data die het gelezen heeft om deze te overschrijven met de versleutelde gegevens.

Deze oproep mislukt weer, dus de bestandsaanwijzer wordt niet verplaatst en staat nog steeds aan het einde van het blok dat net werd gelezen. Ook hier vindt geen controle op foutmeldingen plaats. Vervolgens wordt de versleutelde data niet over de originele data heen geschreven, maar erachteraan, over andere data die dan nergens anders meer beschikbaar is, hetzij versleuteld, hetzij onversleuteld. Dan mislukt ook het aanroepen van de SetFilePointer voor de Nemty-footer en wordt er nog een deel van de onversleutelde data overschreven met de footer, wat leidt tot nog meer verlies van data.

De onderstaande afbeelding toont het effect van deze bug (niet op schaal):

Wij geloven dat 2147586048 bytes de kleinste bestandsgrootte is waarbij het proces misloopt waardoor onomkeerbare versleuteling wordt veroorzaakt; het zou mogelijk moeten zijn om alle bestanden met een originele grootte kleiner dan 2147586048 bytes op de juiste manier te ontsleutelen. Afhankelijk van het type bestand en het deel van de data dat verloren gaat, kan de onherstelbare data slechts een kleine hinder vormen (een hapering in een film bijvoorbeeld), of het kan het hele bestand onbruikbaar maken, zelfs na het ontsleutelen van de data die wel kan worden hersteld. Slachtoffers kunnen niet anders dan proberen hun grote bestanden te ontsleutelen en hopen op het beste.

Indicators of Compromise

SHA256 of the Nemty 1.6 binary used in this research: 98f260b52586edd447eaab38f113fc98b9ff6014e291c59c9cd639df48556e12

Close Menu