
Sumérgete en la fuente CFF y, de paso, aprende sobre un bof en el parsing de CFF de algún jailbreak. Solo por diversión.
Sumérgete en CFF font y, de paso, aprende sobre un bof en el parsing de cff de algún jailbreak. Solo por diversión.
Así que... ¿qué demonios es CFF? Desde mi conocimiento, es un formato de archivo, pero intentemos entender lo que dice Wikipedia: "CFF actúa como un contenedor para almacenar múltiples fuentes juntas en una sola unidad conocida como FontSet." (https://docs.fileformat.com/font/cff/) así que básicamente podemos almacenar fuentes juntas. Genial, ¿y ahora qué? Bueno, como es un formato de archivo, tiene que tener alguna especificación, y sí, la tiene, y no, no es agradable porque son 60 páginas y no estoy dispuesto a aprender su formato. Pero podemos usar el exploit del repositorio de GitHub star-master (código fuente del jailbreak 2.0, creo) para aprender sobre su formato.
Así que... Empezamos usando el script cff.py proporcionado por el autor y también abrimos el archivo out.cff (archivo .cff estándar simple) en el editor hxd.
Podemos ver que cuando alimentamos el archivo al parser obtenemos lo siguiente

Así que ya hemos avanzado un poco, ya que podemos llegar a una definición de CFF y concluir que tenemos algunos metadatos sobre su cabecera que indican las versiones de tff, supongo, el tamaño de cabecera y absoffsize, sea lo que sea eso.
Así que hasta ahora |versión mayor (1 byte)|versión menor (1 byte)|tamaño de cabecera (1 byte) | absoffsize de cabecera (1 bytes) |
Luego vamos más allá y tenemos una función que obtiene qué fuentes se están usando en este archivo .cff. Como se ve

Entonces, ¿de dónde sabemos que su propósito es leer qué fuentes se están usando? Bueno, al ejecutar la herramienta obtuvimos el siguiente resultado en cmd

que vemos que son los siguientes bytes después de los metadatos del archivo

Volveremos más tarde al análisis de esta función, pero por ahora podemos deducir que el formato de archivo es |versión mayor (1 byte)|versión menor (1 byte)|tamaño de cabecera (1 byte) | absoffsize de cabecera (1 bytes) | ABCDEF+fuentes en el paquete|
Además, vemos que buscamos lo que se llama "string" en el script:

¿Por qué es eso? Porque supongo que quieren recopilar información sobre la fuente utilizada en el paquete. De la documentación, dicen: "Todas las cadenas, con la excepción de las cadenas FontName y CIDFontName que aparecen en el Name INDEX, utilizadas por diferentes fuentes dentro del FontSet, se recogen juntas en una estructura INDEX y se referencian mediante un número sin signo de 2 bytes llamado identificador de cadena o SID. Estas cadenas, conocidas como cadenas estándar, describen todos los nombres utilizados en los conjuntos de caracteres ISOAdobe y Expert". De todos modos, un dato interesante aquí es que nos saltamos aproximadamente 41 bytes para llegar a la cadena.


Luego obtenemos información sobre las fuentes presentes en el archivo .cff

¿Cómo demonios hacemos eso? Bueno, recopilamos lo que se llama "top dict data". ¿Qué demonios es eso? Bueno, por lo que pude entender de la documentación, es un dict de Python con cierta información, codificada de cierta manera.

Casualmente, si seguimos el algoritmo de decodificación:

desreferenciamos el tipo de datos de cadenas para obtener información sobre la fuente, por lo que concluimos que los topdicts simplemente tienen algunos índices que luego se usan en el tipo de datos de cadenas para obtener información sobre la fuente.
Así que hasta ahora la definición del archivo sigue en pie:
|versión mayor (1 byte)|versión menor (1 byte)|tamaño de cabecera (1 byte) | absoffsize de cabecera (1 bytes) | ABCDEF+fuentes en el paquete|41 bytes conocidos|25 bytes de información sobre la fuente|
Genial, ¿y qué pasa después? Bueno, si inspeccionamos el script del parser, vemos que obtiene charstring_off, private_off, e irá a la posición charstring_off y leerá algunas cosas más

Pero, ¿cómo nos ayuda esto a tener una visión más amplia? Así que concluiré esto abruptamente. Básicamente hice un diff entre 2 archivos, un cff normal y el cff corrupto.

A la izquierda está el archivo .cff corrupto y a la derecha un archivo .cff normal. Si inspeccionamos el resultado en tiempo de ejecución

La primera vez que ejecutamos el parse vemos que todo, como count, offsize, offbase, son simplemente algunos offsets hasta ciertos delimitadores. ¿Qué delimitadores? Precisamente el nombre de la fuente. Así que, como puedes ver, ('offbase', 8L), es decir, desde el inicio del archivo hasta la primera aparición de la cadena de la fuente presente en el archivo cff

Como también se puede ver en la "segunda ejecución del parse"

vemos en el offset 0x6c en el hexviewr el comienzo \x0e\0xe\0xe\x0e de nuestros datos maliciosos
Así que, como conclusión, un formato general del archivo .cff:
|versión mayor (1 byte)|versión menor (1 byte)|tamaño de cabecera (1 byte) | absoffsize de cabecera (1 bytes) | ABCDEF+fuentes en el paquete|41 bytes conocidos|25 bytes de información sobre la fuente|
Y formato de archivo personalizado (con contenido de usuario):
|versión mayor (1 byte)|versión menor (1 byte)|tamaño de cabecera (1 byte) | absoffsize de cabecera (1 bytes) | ABCDEF+fuentes en el paquete|41 bytes desconocidos|25 bytes de información sobre la fuente| 4 bytes(count) | 4 bytes(offsize) | 9 bytes pendientes de documentar| contenido de usuario|