
CVE-2018-4248: Lecture hors limites dans libxpc lors de la sérialisation de chaînes.
xpc-string-leak est un exploit de preuve de concept pour une lecture mémoire hors limites dans libxpc. Cet exploit utilise la vulnérabilité pour lire de la mémoire tas hors limites depuis diagnosticd, un processus root non sandboxé avec l'attribut task_for_pid-allow.
Sur macOS 10.13.5 et iOS 11.4, la fonction _xpc_string_deserialize ne vérifie pas que la chaîne désérialisée a la longueur appropriée avant de créer un objet XPC string avec _xpc_string_create. Cela peut conduire à une lecture tas hors limites de type heartbleed si la chaîne XPC est ensuite sérialisée dans un autre message XPC.
Voici l'implémentation de _xpc_string_deserialize, décompilée avec IDA :
OS_xpc_string *__fastcall _xpc_string_deserialize(OS_xpc_serializer *xserializer)
{
OS_xpc_string *xstring; // rbx@1
char *string; // rax@4
char *contents; // [rsp+8h] [rbp-18h]@1
size_t size; // [rsp+10h] [rbp-10h]@1 MAPDST
xstring = 0LL;
contents = 0LL;
size = 0LL;
if ( _xpc_string_get_wire_value(xserializer, (const char **)&contents, &size) )
{
if ( contents[size - 1] || (string = _xpc_try_strdup(contents)) == 0LL )
{
xstring = 0LL;
}
else
{
xstring = _xpc_string_create(string, size - 1);
LOBYTE(xstring->flags) |= 1u;
}
}
return xstring;
}
_xpc_string_deserialize appelle d'abord _xpc_string_get_wire_value pour obtenir un pointeur vers les données de la chaîne ainsi que la taille sérialisée de la chaîne, telle que rapportée par l'en-tête de la chaîne. _xpc_string_deserialize vérifie ensuite que la chaîne a un terminateur nul à la fin de sa taille rapportée, mais de manière cruciale ne vérifie pas qu'il n'y a pas de terminateur nul plus tôt dans les données. Enfin, elle crée une copie de la chaîne sur le tas et crée l'objet OS_xpc_string en utilisant _xpc_string_create.
Voici le code décompilé pour _xpc_string_create :
OS_xpc_string *__fastcall _xpc_string_create(const char *string, size_t length)
{
OS_xpc_string *xstring; // rax@1
xstring = (OS_xpc_string *)_xpc_base_create(&OBJC_CLASS___OS_xpc_string, 16LL);
if ( (((_DWORD)length + 4) & 0xFFFFFFFC) + 4 < length )
_xpc_api_misuse("Unreasonably large string");
xstring->wire_length = ((length + 4) & 0xFFFFFFFC) + 4;
xstring->string = string;
xstring->length = length;
return xstring;
}
_xpc_string_create fait confiance à la valeur de longueur fournie par _xpc_string_deserialize et définit les champs appropriés dans l'objet OS_xpc_string. À ce stade, la chaîne désérialisée peut avoir un champ length plus grand que les données de chaîne allouées.
Théoriquement, cela pourrait être utilisé pour déclencher une corruption mémoire dans les services qui obtiennent la longueur de la chaîne en utilisant xpc_string_get_length, mais ce modèle semble rare. Une stratégie d'exploitation moins puissante mais plus pratique consiste à faire en sorte que la chaîne soit re-sérialisée et renvoyée, nous donnant une fenêtre de type heartbleed dans la mémoire du processus victime.
Voici l'implémentation de _xpc_string_serialize :
void __fastcall _xpc_string_serialize(OS_xpc_string *string, OS_xpc_serializer *serializer)
{
int type; // [rsp+8h] [rbp-18h]@1
int size; // [rsp+Ch] [rbp-14h]@1
type = *((_DWORD *)&OBJC_CLASS___OS_xpc_string + 10);
_xpc_serializer_append(serializer, &type, 4uLL, 1, 0, 0);
size = LODWORD(string->length) + 1;
_xpc_serializer_append(serializer, &size, 4uLL, 1, 0, 0);
_xpc_serializer_append(serializer, string->string, string->length + 1, 1, 0, 0);
}
Le paramètre length de OS_xpc_string est considéré comme fiable lors de la sérialisation, ce qui signifie que de nombreux octets sont lus depuis le tas dans le message sérialisé. Si la chaîne désérialisée était plus courte que sa longueur rapportée, le message sera rempli de données tas hors limites.
Nous sommes toujours limités à l'exploitation de services XPC qui renvoient une partie du message XPC au client, mais cela est beaucoup plus courant. Par exemple, sur macOS et iOS, diagnosticd est un candidat prometteur qui est également non sandboxé, root, et possède les privilèges task_for_pid. Diagnosticd est responsable du traitement des messages de diagnostic (par exemple, les messages générés par os_log) et de les diffuser aux clients intéressés par la réception de ces messages. En s'enregistrant pour recevoir notre propre flux de diagnostic, puis en envoyant un message de diagnostic avec une chaîne plus courte que prévu, nous pouvons obtenir un instantané de certaines données dans le tas de diagnosticd, ce qui peut aider à obtenir une exécution de code dans le processus.
Pour construire, exécutez make. Consultez le haut du Makefile pour diverses options de construction.
Exécutez l'exploit en spécifiant la taille de la fuite sur la ligne de commande :
$ ./xpc-string-leak 0x40
0x2000000000000000 0xe00007ff39bf0992
0x00007fff56858570 0x00007fff7ed23d0e
0x0000000000000000 0x0000000000000000
0x00007fff7ed52be2 0x00007fff7ed29ed6
La taille de la fuite doit être un multiple de 8 et au moins 16.
Le code de xpc-string-leak est placé dans le domaine public. Par courtoisie, je demande que si vous référencez ou utilisez une partie de ce code, vous m'en attribuiez le crédit.
J'ai découvert ce bogue au début de 2018 (janvier ou février), mais j'ai oublié de l'étudier jusqu'en mai. J'ai signalé le problème à Apple le 9 mai, et il a été attribué CVE-2018-4248 et corrigé dans iOS 11.4.1 et macOS 10.13.6 le 9 juillet.
Brandon Azad