
Plongez dans la police CFF et apprenez au passage un bof dans l'analyse CFF grâce à un jailbreak. juste pour le fun
Plongeons dans la police CFF et apprenons au passage à propos d'un buffer overflow dans le parsing du CFF à partir d'un jailbreak. juste pour le fun.
Donc.... c'est quoi CFF ? De ce que je sais, c'est un format de fichier, mais essayons de comprendre ce que dit Wikipédia : "CFF agit comme un conteneur pour stocker plusieurs polices ensemble dans une unité unique appelée FontSet. "(https://docs.fileformat.com/font/cff/) donc en gros, on peut stocker des polices ensemble. cool, et maintenant ? Eh bien, comme c'est un format de fichier, il doit avoir une spec, et oui, il en a une, et non, elle n'est pas sympa parce qu'elle fait 60 pages et je n'ai pas envie d'apprendre son format. Mais on peut utiliser l'exploit du repo github star-master (code source du jailbreak 2.0 je pense) afin d'apprendre son format.
Donc... On commence par utiliser le script cff.py fourni par l'auteur et on ouvre aussi le fichier out.cff (fichier .cff simple standard) dans l'éditeur hxd.
On peut voir que lorsqu'on passe le fichier au parse, on obtient ce qui suit

Donc on a déjà fait quelques progrès, car on peut déjà proposer une définition pour CFF, car on peut en conclure qu'on a des métadonnées sur son en-tête qui indiquent les versions tff, je présume, la taille de l'en-tête et absoffsize, quoi que ça puisse être.
Donc jusqu'à présent |version majeure(1 octet)|version mineure(1 octet)|taille de l'en-tête(1 octet) | absoffsize de l'en-tête(1 octet) |
On va ensuite plus loin et on a une fonction qui récupère les polices utilisées dans ce fichier .cff. Comme on le voit

Alors d'où sait-on que son but est de lire quelles polices sont utilisées ? Eh bien, en exécutant l'outil, on a obtenu le résultat suivant dans cmd

que l'on voit être les octets qui viennent après les métadonnées du fichier

Nous reviendrons plus tard sur l'analyse de cette fonction, mais pour l'instant on peut déduire que le format du fichier est |version majeure(1 octet)|version mineure(1 octet)|taille de l'en-tête(1 octet) | absoffsize de l'en-tête(1 octet) | ABCDEF+polices dans le pack|
Ensuite, on voit qu'on recherche ce qui est appelé string dans le script :

Pourquoi ça ? Parce que je présume qu'ils veulent rassembler des infos sur la police utilisée dans le pack. D'après la doc, ils disent : "Toutes les chaînes, à l'exception des chaînes FontName et CIDFontName qui apparaissent dans l'INDEX des noms, utilisées par différentes polices au sein du FontSet, sont regroupées dans une structure INDEX et référencées par un nombre non signé de 2 octets appelé identifiant de chaîne ou SID. Ces chaînes, connues sous le nom de chaînes standard, décrivent tous les noms utilisés dans les jeux de caractères ISOAdobe et Expert" . Bref, une chose intéressante à noter ici est qu'on a sauté environ 41 octets pour arriver à la chaîne.


Ensuite, on obtient des informations sur les polices présentes dans le fichier .cff

Comment on fait ça, bordel ? Eh bien, on collecte ce qu'on appelle les données top dict. C'est quoi ce truc ? Eh bien, d'après ce que j'ai pu comprendre de la doc, c'est un dict Python avec certaines informations dedans, encodé d'une certaine manière.

Par coïncidence, si on suit l'algo de décodage :

on déréférence le type de données strings pour obtenir des infos sur la police, donc on en conclut que les topdicts contiennent simplement quelques index qui sont ensuite utilisés dans le type de données strings pour obtenir des infos sur la police.
Donc jusqu'à présent, la définition du fichier tient toujours,
|version majeure(1 octet)|version mineure(1 octet)|taille de l'en-tête(1 octet) | absoffsize de l'en-tête(1 octet) | ABCDEF+polices dans le pack|41 octets connus|25 octets d'infos sur la police|
Cool, et maintenant, qu'est-ce qui se passe ? Eh bien, si on inspecte le script du parseur, on voit qu'il récupère charstring_off, private_off et qu'il se rend à la position charstring_off pour lire d'autres trucs.

Mais en quoi ça nous aide à avoir une vue d'ensemble ? Donc je vais conclure ça brusquement. En gros, j'ai fait un diff entre 2 fichiers, un cff normal et le cff corrompu.

À gauche se trouve le fichier .cff corrompu et à droite un fichier .cff normal. Si on inspecte le résultat à l'exécution

Au premier lancement du parse, on voit que tout ce qui est count, offsize, offbase n'est que des offsets jusqu'à certains délimiteurs. Quels délimiteurs ? Précisément le nom de la police. Donc, comme on peut le voir ('offbase', 8L), du début du fichier jusqu'à la première rencontre de la chaîne du nom de la police présente dans le fichier cff

Comme on peut aussi le voir dans "deuxième exécution du parse"

on voit à l'offset 0x6c dans hexviewr le \x0e\0xe\0xe\x0e début de nos données malveillantes
Donc en conclusion, un format général du fichier .cff
|version majeure(1 octet)|version mineure(1 octet)|taille de l'en-tête(1 octet) | absoffsize de l'en-tête(1 octet) | ABCDEF+polices dans le pack|41 octets connus|25 octets d'infos sur la police|
Et le format de fichier personnalisé (avec contenu utilisateur)
|version majeure(1 octet)|version mineure(1 octet)|taille de l'en-tête(1 octet) | absoffsize de l'en-tête(1 octet) | ABCDEF+polices dans le pack|41 octets inconnus|25 octets d'infos sur la police| 4 octets(count) | 4 octets(offsize) | 9 octets restants à documenter| contenu utilisateur|