Apple załatało CVE-2025-43300 20 sierpnia 2025 r., wprowadzając out‑of‑band patch na wszystkie główne systemy – iOS, iPadOS, macOS, tvOS. WhatsApp dorzucił swój fix kilka dni później, przyznając wprost, że jego podatność do łańcucha ataku dokładana była właśnie z tym bugiem w ImageIO. Najciekawsze? Sedno problemu to rozjazd między metadanymi TIFF/DNG a wewnętrznym strumieniem JPEG lossless – rzecz, którą większość devów zbyłaby wzruszeniem ramion.
Jeśli zajmujesz się ofensywką, detections albo hardeningiem urządzeń Apple w organizacji, ten case jest idealnym przykładem, jak bardzo “mały” błąd w głębokim, nudnym kawałku stacku (RawCamera, DNG, JPEG SOF3…) przekłada się na realne ryzyko biznesowe. W tym tekście nie robimy laurki Apple – rozbieramy ImageIO, pokazujemy, co dokładnie poszło nie tak, jak wyglądał patch i co z tego wynika dla Ciebie jako blue/red teamu.
DNG, JPEG lossless i dlaczego metadane naprawdę Cię obchodzą
CVE-2025-43300 siedzi w ImageIO – systemowym frameworku Apple, który ogarnia czytanie, zapisywanie, dekodowanie i metadane obrazów w iOS/macOS/iPadOS. To nie jest jakaś „biblioteka od zdjęć w jednej aplikacji”. To fundament, którego używa Photos, Safari, Messages, AirDrop, WhatsApp i cała reszta, wszędzie tam, gdzie system musi ruszyć obrazek.
Konkretny pacjent to RawCamera.bundle i klasa CDNGLosslessJpegUnpacker, która zajmuje się przetwarzaniem DNG – formatu Adobe opartego na TIFF/EP, wykorzystywanego m.in. do zdjęć RAW. DNG może trzymać dane skompresowane JPEG‑iem lossless (SOF3), a decoder korzysta przy tym z dwóch źródeł prawdy: tagów TIFF (np. SamplesPerPixel, BitsPerSample, RowsPerStrip/TileWidth/TileLength) oraz wewnętrznego strumienia JPEG, gdzie w markerze SOF3 deklarowana jest m.in. liczba komponentów (NumComponents).
I tu jest pies pogrzebany: jedna część kodu ufa TIFF‑owi (“SamplesPerPixel = 2, więc dwa kanały na piksel”), inna ufa strumieniowi JPEG (“NumComponents = 1, mamy jeden komponent”) i przy odpowiednio złośliwej kombinacji te dwa światy rozjeżdżają się jak źle skonfigurowane routing tables. Bufor alokowany jest według jednego zestawu założeń, pętle zapisujące dane iterują według innego – efekt końcowy to out‑of‑bounds write w trakcie dekompresji jednostek obrazu.
Jak dokładnie wyglądał bug: od SamplesPerPixel do OOB write
Quarkslab i niezależni badacze ładnie rozpisali call‑tree, ale sedno da się sprowadzić do jednego zdania: jeśli w DNG ustawisz SamplesPerPixel = 2, a w JPEG SOF3 NumComponents = 1, to kod w ImageIO zaczyna żyć w schizofrenii i pisze poza przydzieloną pamięcią.
W skrócie wygląda to tak:
– RawCamera alokuje bufor uncompressed o rozmiarze width * height * SamplesPerPixel (czyli bazuje na TIFF, który mówi: na piksel mamy 2 próbki).– W pętli po wysokości (height) dekompresuje wiersz po wierszu, korzystając z tablic Huffmana zdefiniowanych w markerach DHT i parametrów z SOF3 (tam NumComponents = 1).– Pierwszy piksel w wierszu jest przetwarzany w pętli po komponentach – ale komponent jest jeden, więc wszystko się jeszcze spina.– Dalej wchodzi w gałąź kodu, która jest napisana z założeniem „mamy co najmniej 2 komponenty na piksel” – i to jest krytyczny moment.
Zamiast ograniczać się do NumComponents, wewnętrzna pętla “przeskakiwania” po pikselach używa mnożnika width * 2 (czyli zakodowane na sztywno „2 komponenty”), a funkcja decompress() zwraca ilość danych odpowiadającą SamplesPerPixel, czyli dwóm 16‑bitowym próbką na piksel. W efekcie:
– limiter pętli opiera się na NumComponents = 1,– ale ilość danych zapisywanych na wyjściu jest wyliczana jak dla SamplesPerPixel = 2 i 16 bitów na próbkę (2 bajty na kanał).
To oznacza, że dla każdego wiersza dekoder wypycha do bufora mniej więcej dwa razy więcej danych, niż pierwotnie założył przy alokacji – klasyczny out‑of‑bounds write (CWE‑787), tylko zakopany głęboko w ścieżce dekodowania JPEG lossless w DNG.
Co gorsza, ta ścieżka nie jest egzotycznym edge‑casem. Kod obsługuje tzw. “Lossless DNG Tile Unpacker Queue” – kolejkę, która dzieli obraz na kafelki (tiles) i równolegle je rozpakowuje, wstawiając w odpowiednie miejsca w buforze wyjściowym. Jeśli któryś z kafelków zostanie zasilony takim złośliwym układem SamplesPerPixel/NumComponents, nadpisanie pamięci może dotknąć nie tylko samego bufora pikseli, ale także struktury zarządzające kolejką lub inne obiekty w tym samym regionie pamięci, co już otwiera drogę do RCE.
Dwa bajty patcha, duże kłopoty dla ofensywy
Najbardziej smakowite w tym case’ie jest to, jak niewielki diff wprowadziło Apple, żeby wybić z ręki tę klasę bugów. Quarkslab, Hackyboiz i inni badacze przejrzeli diff RawCamera między iOS 18.6.1 a 18.6.2 / odpowiadającymi buildami macOS – wyszło, że istotnie zmienionych jest tak naprawdę kilka funkcji, z czego dwie są kluczowe: odpowiedzialne za dekodowanie JPEG lossless oraz ścieżka renderowania pixel bufferów.
Po pierwsze, Apple przesiadło się z “gołego” pipeline’u CGImage + CFData na pipeline CMPhoto + CVPixelBuffer w części odpowiedzialnej za obsługę niektórych formatów. Stary kod sprawdzał mniej więcej: czy CFDataGetLength(data) == 4 * width * height – zakładając 4 bajty na piksel, ignorując padding w wierszach (BytesPerRow) i różne formaty bufora. Nowa wersja bierze CVPixelBuffer, wywołuje CVPixelBufferGetBytesPerRow i CVPixelBufferGetDataSize, a potem porównuje DataSize z wyliczoną wewnętrznie wartością „expected”, uwzględniającą stride i format. Jeśli coś się nie zgadza, leci RawCameraException zamiast spokojnego przejścia dalej.
Po drugie, w samej logicznej ścieżce dekompresji Apple dorzuciło klasyczny „bezpiecznik”: przed każdym zapisem do bufora sprawdzana jest faktyczna granica – dodatkowe wywołania pomocniczych funkcji, które rezerwują bufor i raportują jego rozmiar, a warunek typu „jeżeli pointer przekroczył koniec, rzuć wyjątek”. W praktyce to sprowadza się do dodania jednego dodatkowego porównania na pointerze i skoku do handlera wyjątku – dokładnie tych „dwóch bajtów”, o których mówili badacze, choć oczywiście całościowo patch jest odrobinę większy.
Z perspektywy ofensywnej to jest ciekawy sygnał: Apple nie wprowadza tu żadnej kosmicznej nowej technologii, tylko przenosi dekodowanie na api, które z natury wymusza świadomość rozmiaru bufora (CVPixelBuffer) i dokręca klasyczne bounds‑checking. To oznacza, że w 2026 r. polowanie na podobne bugi w ImageIO czy innych krytycznych frameworkach będzie wymagało szukania bardziej subtelnych bugów logicznych, a nie prostych matematycznych wpadek w pętlach.
Zero‑click łańcuch: jak z obrazka w WhatsAppie zrobić pełne przejęcie
Sama podatność w ImageIO to tylko połowa historii. Druga połowa siedzi w kliencie, który umożliwia dociągnięcie i automatyczne przetworzenie obrazka bez interakcji użytkownika. W tym przypadku w łańcuch wchodził WhatsApp – jego własna luka pozwalała na wymuszenie pobrania zasobu z dowolnego URL‑a i odpalenie jego przetwarzania, a ImageIO robiło resztę.
Schemat na poziomie taktycznym wygląda mniej więcej tak:
– Tzw. delivery/initial access: atakujący wysyła do ofiary specjalnie przygotowaną wiadomość w WhatsAppie; klient automatycznie pobiera obraz DNG (albo plik przetworzony do DNG w trakcie pipeline’u) z kontrolowanego przez atakującego serwera.– Exploitation: przy przetwarzaniu obrazka (np. generowanie miniatury, analiza metadanych, przygotowanie do wyświetlenia w UI) WhatsApp woła systemowy ImageIO, który odpala dekodowanie DNG → wchodzi w podatną ścieżkę Lossless DNG Tile Unpacker Queue, odpala out‑of‑bounds write.– Post‑exploitation: jeśli dopiszesz do tego łańcucha odpowiedni ROP/JavaScriptCore/IOKit czy cokolwiek jeszcze potrzebne do pełnego sandbox escape i persistence, kończysz z typowym spyware‑owym scenariuszem – przejęcie komunikatorów, mikrofonu, aparatu, backupów itd.
To jest typowy zero‑click z gatunku „nie musisz zrobić absolutnie nic złego jako użytkownik, wystarczy być na liście celów”. Amnesty i inni sygnalizowali, że kampanie wykorzystujące CVE‑2025‑43300 były ukierunkowane na konkretne osoby, a nie randomowe masowe skanowanie internetu. Dla zespołów bezpieczeństwa to nie jest klasyczny problem „usera, który kliknął w phishing”, tylko czysta gra w klasie APT/spyware.
Co z tego wynika dla SOC, IR i devów?
Dla SOC‑u i IR‑u pierwsza, bardzo przyziemna konsekwencja: zero‑click na poziomie ImageIO jest z definicji słabo widoczny w klasycznych telemetrycznych śladach użytkownika. Nie ma URL‑i klikanych w Safari, nie ma dziwnych tapów, nie ma „otwarł PDF z maila”. Jest normalny traffic do CDN‑ów WhatsAppa i systemowy proces przetwarzający obrazki. Jeśli nie masz dobrej widoczności na poziomie MDM/EDR dla iOS z logami crashy, exceptionów z RawCamera/ImageIO czy zachowań WhatsAppa, jesteś w zasadzie ślepy.
Po drugie, i to powinni sobie wziąć do serca zarówno devowie, jak i ci, którzy klepią secure coding guidelines: ten bug to książkowy przykład, dlaczego „podwójne źródło prawdy” o strukturze danych (tu: metadane TIFF vs. wewnętrzny strumień JPEG) musi mieć twardą walidację spójności. Jeżeli jedna warstwa ufa SamplesPerPixel, a druga NumComponents i nikt nie robi cross‑checka, to prędzej czy później wpadniesz w taką minę.
Z bardzo praktycznych rzeczy, które możesz zrobić już teraz:
– W procesach IR potraktuj CVE‑2025‑43300 jako trigger do retro‑analizy crashy i logów na flotach iOS/macOS w okresie sprzed patcha (18.6.2 / odpowiadające wersje macOS). Szukaj nietypowych błędów RawCameraException, crashy w ImageIO/RawCamera w kontekście komunikatorów.– Jeśli jesteś po stronie MDM/EDR – loguj i koreluj wywołania do ImageIO oraz nietypowe błędy podczas przetwarzania obrazów z komunikatorów i aplikacji „wysokiego ryzyka” (WhatsApp, iMessage, Signal, inne). To nie jest piękne ani proste na iOS, ale ignorowanie tego wektoru to proszenie się o kłopoty.– Jeśli tworzysz aplikacje korzystające z ImageIO lub własne parsowanie obrazów, po prostu przestań zakładać, że metadane mówią prawdę. Krzyżowo waliduj wartości takie jak SamplesPerPixel, BitsPerSample, NumComponents, RowsPerStrip/TileLength, a bufor alokuj na podstawie najbardziej konserwatywnych założeń, a nie najsympatyczniejszych dla wydajności.
Wreszcie – aspekt biznesowy. To nie jest bug klasy „userowi źle się wyświetli ikonka”. To jest luka, która wprost przekłada się na: ryzyko wycieku komunikacji zarządu, podsłuchów narzędzi zarządzania kryzysowego, kompromitacji urządzeń, które ze względu na politykę BYOD/COPE są używane jednocześnie prywatnie i służbowo. Jeśli Twoja organizacja zakłada, że „iPhone’y są w miarę bezpieczne same z siebie”, to CVE‑2025‑43300 jest kolejnym argumentem, że bez procesu patchowania, wymuszania wersji i kontroli aplikacji to jest czyste życzeniowe myślenie.
Gdzie jest granica zaufania do platformy?
Ta historia ma bardzo prostą puentę. Jeśli framework od obrazków w systemie klasy iOS potrafi popełnić tak trywialny błąd jak pomylenie SamplesPerPixel z NumComponents i nieprzeliczenie limitu pętli, to nie ma żadnego powodu, żebyś traktował „systemowe” jako z definicji „bezpieczne”.
Z punktu widzenia red‑teamów i exploit devów to jest żyzna gleba: parsowanie złożonych formatów, głębokie frameworki, stare założenia projektowe i minimalne testy w branchach, które „przecież i tak prawie nikt nie używa”. Z punktu widzenia blue‑teamu – sygnał, że bez twardych wymogów aktualizacji (policy dla iOS 18.6.2+), ograniczania komunikatorów, które mają historię takich łańcuchów, i realnej obserwowalności na endpointach, grasz w ruletkę.
Jeżeli po lekturze tej historii jedyne, co zrobisz, to dopiszesz dwa zdania w polityce „zalecamy aktualizację urządzeń”, to znaczy, że kompletnie nie zrozumiałeś stawki. Traktuj CVE‑2025‑43300 jako modelowy case: przeanalizuj go z zespołem, zmapuj, które Twoje procesy by go wyłapały, a które kompletnie przegapiły. A potem popraw te ostatnie – zanim następny „niewinny obrazek” zrobi Ci RCE w najważniejszym telefonie w firmie.
Bibliografia:
– Quarkslab: Reverse engineering of Apple's iOS 0-click CVE-2025-43300https://blog.quarkslab.com/patch-analysis-of-Apple-iOS-CVE-2025-43300.html– Hackyboiz: iOS/macOS DNG Image Processing Memory Corruption (CVE-2025-43300)https://hackyboiz.github.io/2025/10/23/clalxk/imageIO_en/– Cryptika: Analysis of Apple's ImageIO Zero-Day Vulnerabilityhttps://www.cryptika.com/analysis-of-apples-imageio-zero-day-vulnerability-attacker-context-and-historical-ios-zero-click-similarities/– Fidelis Security: CVE-2025-43300 Zero-Day: ImageIO Exploit Alerthttps://fidelissecurity.com/vulnerabilities/cve-2025-43300/– Wiz: CVE-2025-43300 Impact, Exploitability, and Mitigation Stepshttps://www.wiz.io/vulnerability-database/cve/cve-2025-43300– Malwarebytes: WhatsApp fixes vulnerability used in zero-click attackshttps://www.malwarebytes.com/blog/news/2025/09/whatsapp-fixes-vulnerability-used-in-zero-click-attacks– The Hacker News: WhatsApp Patches Zero-Click Exploit Targeting iOS and macOS Usershttps://thehackernews.com/2025/09/whatsapp-issues-emergency-update-for.html– PKF: Understanding Apple's ImageIO Framework CVE-2025-43300https://pkfthailand.asia/cve-2025-43300-vulnerability-understanding-apples-imageio-framework/