PDFs "a pelo" for fun and profit (Parte II)

Publicado por Pedro C. el 11-03-2013

Continuamos publicando una serie de artículos, para que podais crear vuestros propios ficheros PDFs desde cero y "a mano" para poder comprender cómo funciona el formato y poder embeber código en ellos para ejecutar ciertas acciones y algunas de ellas muy interesantes como veremos en las entregas.

Como continuación de nuestro anterior artículo vamos en ésta entrada a definir filtros y dar nuestros primeros pasos incluyendo código con JavaScript.

¿Qué es un filtro?

Como ya comentamos anteriormente, un stream es un diccionario y datos externos de la forma:

15 0 obj
     ....
>>
stream
     Los datos van aquí...
endstream
endobj				
				

Por tanto, un stream puede ser empleado para contener otro fichero, contenido multimedia, imágenes, etc... Por ejemplo, si queremos incluir una imágen JPG como nuestro querido borriquito de Peret, necesitaremos un algoritmo para poder decodificarlo. Tenemos incluídos unos cuantos filtros como ASCIIHexDecode, ASCII85Decode, LZWDecode, FlateDecode, RunLengthDecode, CCITTFaxDecode, JBIG2Decode, DCTDecode y JPXDecode.

Texto raw en los streams

En nuestro último ejemplo que pusimos, podemos observar el texto en formato raw sin ningún tipo de filtro:

4 0 obj
<< /Length 56 >>
stream
 BT
  /F1 50 Tf
  100 790 Td (Hola MADESYP!!!) Tj
 ET
endstream
endobj				
				

Como es lógico, nadie abre un fichero PDF con un editor de textos a no ser que sea un paranóico, pero como "haberlos haylos", en caso de querer incluir algún código malicioso, enseguida se nos verían las intenciones y no conseguiríamos jamás que fuera abierto por un lector apropiado que ejecutase dichas acciones.

Representación Hexadecimal

Una cadena puede ser guardada en Hexadecimal como la anterior, "Hola MADESYP!!!" que quedaría convertida a "486f6c61204d414445535950212121" (recordar que 1 carácter de la cadena original queda convertida en 1 byte y representada por dos caracteres. Por ejemplo, "a"="61"

Modificaremos el anterior objeto para guardar dicha cadena en Hexadecimal entre los símbolos "<" y ">" quitando los paréntesis anteriores, calcularemos la nueva longitud del stream y recalcularemos la tabla de referencias cruzadas para apuntar correctamente a los offsets correctos junto con el inicio de la misma.

4 0 obj
<< /Length 71 >>
stream
 BT
  /F1 50 Tf
  100 790 Td <486f6c61204d414445535950212121> Tj
 ET
endstream
endobj				

[...]

0000000186 00000 n
0000000322 00000 n
0000000451 00000 n

[...]

startxref
529
%%EOF
				

En éste punto, recomendaros encarecidamente que PROBEIS a hacerlo a partir del ejemplo 3 hasta que os salga, pero si no fuera posible, podeis descargar el ejemplo 4 ya completado y con los offsets correctos.

Representación Octal

Una cadena también puede ser expresada en Octal como la anterior, "Hola MADESYP!!!" que quedaría convertida a "\110\157\154\141\040\115\101\104\105\123\131\120\041\041\041" (recordar que 1 carácter de la cadena original queda convertida en 1 byte y representada por dos ó tres caracteres (mejor) precedidos por el símbolo "\" para representar que se trata de Octal. Por ejemplo, "a"="\141"

Modificaremos el anterior objeto para guardar dicha cadena en Octal entre los símbolos "(" y ")" quitando los caracteres anteriores, calcularemos la nueva longitud del stream y recalcularemos la tabla de referencias cruzadas para apuntar correctamente a los offsets correctos junto con el inicio de la misma.

4 0 obj
<< /Length 101 >>
stream
 BT
  /F1 50 Tf
  100 790 Td (\110\157\154\141\040\115\101\104\105\123\131\120\041\041\041) Tj
 ET
endstream
endobj				

[...]

0000000186 00000 n
0000000322 00000 n
0000000482 00000 n

[...]

startxref
560
%%EOF				
				

Volver a recomendaros encarecidamente que PROBEIS, PROBEIS y PROBEIS a hacerlo a partir del ejemplo 3 ó 4 hasta que os salga, pero si no fuera posible, podeis descargar el ejemplo 5 ya completado y con los offsets correctos.

Espacios entre los Nibbles

Es posible "ofuscar" un poco más el código poniendo espacios entre los nibbles (la mitad de un octeto) en vez de representar cada valor hexadecimal seguidos y separados por un espacio como de costumbre. En el formato PDF podemos emplear cualquier tipo de espacio (espacio, nueva línea, tabulador, etc...). Por ejemplo, es lo mismo:

Ejemplo 1:
486f6c61204d414445535950212121

Ejemplo 2:
48 6f 6c 61 20 4d 41 44 45 53 59 50 21 21 21

Ejemplo 3:
4 8      6
f
6c6    
1 
[...]
				

En la respresentación ASCII, podemos combinar tantos caracteres de nueva línea como queramos (tener en cuenta que lo único será conseguir aumentar el peso del documento):

H\
o\
l\
a!!!				
				

Establecer un filtro en un stream

Vamos a emplear un filtro en nuestro stream para "ocultar básicamente" la información que contiene. Decimos básicamente ya que conforme veremos en siguientes entregas, hay herramientas que nos permitirán recuperarla en "texto claro" y no es garantía completa para poder creer que nuestro stream será "opaco" en su contenido.

Por ejemplo, siguiendo con la estructura básica que hemos comenzado, hay que observar todo el contenido del stream que es sobre la base que vamos a trabajar:

4 0 obj
<< /Length 56 >>
stream
 BT
  /F1 50 Tf
  100 790 Td (Hola MADESYP!!!) Tj
 ET
endstream
endobj				
				

En realidad, en el contenido del stream, eliminando los separadores anteriormente vistos, quedaría como el siguiente ya que dejaremos los espacios mínimos para que podais distinguir cada elemento y no codificaremos el texto, sino que lo dejaremos en ASCII:

stream
BT /F1 50 Tf 100 790 Td (Hola MADESYP!!!) Tj ET
endstream				
				

Si convertimos dicha cadena a hexadecimal, veremos que obtenemos la secuencia (incluyendo el espacio entre elementos) "4254202f463120353020546620313030203739302054642028486f6c61204d4144455359502121212920546a204554"

Para crear nuestro primer filtro, que será ASCIIHexDecode y que como estareis suponiendo, se encargará de decodificar de hexadecimal a ASCII. Veamos cómo quedaría el objeto para incluir un filtro:

4 0 obj
<<
	/Length longitud_bytes
	/Filter /Nombre_del_filtro
>>
stream
datos_del_stream_codificados_con_el_filtro>
endstream
endobj				
				

Una vez explicado, volvemos a trabajar con el ejemplo 3 o podeis continuar con los dos anteriores. Vamos a modificar nuestro PDF para que podamos guardar el stream con los datos codificados con el filtro que hemos empleado. Modificaremos el anterior objeto para guardar dicha cadena acabada con el símbolo ">", calcularemos la nueva longitud del stream y recalcularemos la tabla de referencias cruzadas para apuntar correctamente a los offsets correctos junto con el inicio de la misma.

4 0 obj
<< /Length 95 /Filter /ASCIIHexDecode >>
stream
4254202f463120353020546620313030203739302054642028486f6c61204d4144455359502121212920546a204554>
endstream
endobj				

[...]

0000000186 00000 n
0000000322 00000 n
0000000499 00000 n

[...]

startxref
577
%%EOF				
				

PROBAR, PROBAR y PROBAR a hacerlo a partir del ejemplo 3, 4 ó 5 hasta que os salga, pero si no fuera posible, podeis descargar el ejemplo 6 ya completado y con los offsets correctos.

El filtro ASCII85Decode

El filtro ASCII85Decode es una forma de representar datos binarios como texto codificados de forma similar a Base64 y desarrollado por Adobe Systems. Vamos a emplearlo para el mismo ejemplo anterior.

Lo primero vamos a codificar los datos del stream "BT /F1 50 Tf 100 790 Td (Hola MADESYP!!!) Tj ET" en ASCII85. Para ello, emplearemos el conversor online y obtenemos la secuencia:

<~6<#'\7PQ#C0Ha>,+>GQ(+?);7+B2ko-qJ8m@3A916q($W:a$u*.3MT)+@T6~>
				

Se emplean los símbolos "~>" para el EOD y no existe principio (viene dado por el propio stream), por lo que en realidad, en la representación de los datos es necesario quitar el comienzo (BOD) de lo obtenido (los 2 primeros caracteres).

Partiendo del ejemplo anterior, vamos a emplear dicho filtro, recalculando, etc... Nos quedará de la siguiente forma:

4 0 obj
<< /Length 95 /Filter /ASCII85Decode >>
stream
6<#'\7PQ#C0Ha>,+>GQ(+?);7+B2ko-qJ8m@3A916q($W:a$u*.3MT)+@T6~>
endstream
endobj				

[...]

0000000186 00000 n
0000000322 00000 n
0000000464 00000 n

[...]

startxref
542
%%EOF								
				

Podeis descargar el ejemplo 7 anterior con todo recalculado.

El filtro FlateDecode

Nos enfrentamos a uno de los filtros (/FlateDecode) más empleados en el formato PDF y donde tendremos que emplear la librería ZLIB para comprimir/descomprimir los datos de los streams. Hasta ahora, si os habeis fijado, no hemos empleado todavía caracteres chinos por lo que cualquier fichero PDF de los que hemos hecho, puede ser mostrado "sin pitidos" cuando ponemos:

more fichero.pdf				
				

Sin embargo, con el filtro FlateDecode tendremos que emplear Caracteres ASCII extendidos que seguramente, al hacer un "cat", "more", "type", etc... del fichero PDF, salgan "chinos" por pantalla, pitidos, etc... Es decir, tipo binario.

Llegados a éste punto, probar a hacerlo con cualquier fichero PDF que tengais a mano y lo podreis comprobar. Fijaros que "se ve en cristiano" al principio y final, pero seguramente no podamos leer el resto por no entender lo que pone a partir del primer FlateDecode que tenga. Usar un editor de textos para comprobarlo...

Lo primero que vamos a indicar en la segunda línea de nuestro nuevo fichero es que contendrá caracteres NO ASCII de la forma %c1c2c3c4 siendo c1, c2, c3 y c4 cuatro caracteres ASCII iguales o mayores del 128 (los que queramos) aunque la recomendación es usar el conjunto [128,159]:

%PDF-1.7
%éééé				
				

A continuación, vamos a comprimir la secuencia del stream "BT /F1 50 Tf 100 790 Td (Hola MADESYP!!!) Tj ET" con la librería ZLIB. Guardaremos los datos en un fichero llamado asciiraw.txt (47 bytes) y lo subiremos al conversor online, pulsaremos en el botón compress y podremos descargar el fichero binario asciiraw.dat (53 bytes) que emplearemos posteriormente.

Vamos a fraccionar el nuevo fichero en 3 partes para poder entenderlo. La primera será desde el inicio del fichero incluyendo los nuevos caracteres comentados hasta el stream, la segunda serán los datos comprimidos en ZLIB que hemos obtenido y la tercera desde el endstream hasta el final del fichero.

Primera parte (Ejemplo 8-1):

%PDF-v1.7
%éééé

%PDF binario a mano por Academia MADESYP

1 0 obj
<</Type /Catalog
/Pages 2 0 R
>>
endobj

2 0 obj
<</Type /Pages
/Kids [ 3 0 R ]
/Count 1
>>
endobj

3 0 obj
<</Type /Page
/Parent 2 0 R
/MediaBox [ 0 0 595 842 ]
/Resources << /Font << /F1 5 0 R>> >>
/Contents 4 0 R
>>
endobj

4 0 obj
<< /Length 53 /Filter /FlateDecode >>
stream				
				

Segunda parte (Ejemplo 8-2) y NO COPIAR DIRECTAMENTE los caracteres conforme aparecen por incluir caracteres no imprimibles:

x?s
Q?w3T05PIS040P0?2S4<?s|]\?#5B?\C
				

Tercera parte (Ejemplo 8-3):

endstream
endobj

5 0 obj
<<
 /Type /Font
 /Subtype /Type1
 /BaseFont /Arial
>>
endobj

xref
0 6
0000000000 65535 f
0000000064 00000 n
0000000119 00000 n
0000000185 00000 n
0000000321 00000 n
0000000451 00000 n

trailer
<</Size 6
/Root 1 0 R
>>

startxref
529
%%EOF				
				

Vamos a concatenar todos los ficheros en el nuevo Ejemplo 8 dependiendo del sistema operativo que tengamos:

Windows:
copy /b eighth-1.txt + asciiraw.dat + eighth-2.txt eighth.pdf

*nix:
cat eighth-1.txt asciiraw.dat eighth-2.txt > eighth.pdf
				

Habremos obtenido el fichero eighth.pdf (722 bytes) binario y funcionando a la perfección.

Encadenando filtros

Por si no hemos tenido bastante con los ejemplos anteriores, podemos combinar varios filtros para "complicarle" la vida al investigador y que no pueda acceder a los datos que contienen los streams.

La idea básica es combinar un filtro en otro agrupándolos en un array que si recordais del artículo anterior, quedaba representado por los corchetes. Al igual que ocurre con los ataques de XSS, a veces es funcional y nos permite saltar las "herramientas antimalignas" mal desarrolladas.

4 0 obj
<<
        /Filter [ /Filtro1 /Filtro2 /Filtro3 ... /FiltroN ]
        /Length Longitud_Bytes
>>
stream
Datos_del_stream
endstream
endobj				
				

Por ejemplo, podríamos haber combinado la secuencia siguiente de filtros, quedando el stream codificado en hexadecimal, una vez obtenido el ASCII el resultado lo tendríamos todavía codificado en ASCII85, etc...

4 0 obj
<<
        /Filter [ /ASCIIHexDecode /ASCII85Decode /LZWDecode /FlateDecode ]
        /Length 2013
>>
stream
0f1e2d3b4c5a.........
endstream
endobj				
				

Aunque nos parezca una forma segura para asegurar nuestro código, hay que conocer las herramientas que se emplean para obtener los textos en plano y poder realizar su análisis. No todas implementan todos los filtros disponibles, ni todas son capaces de reconocer código ofuscado ya que esperan patrones "fijos", etc... Hace algunos años, en las principales Conferencias de Seguridad, se comentó largo y tendido sobre el formato PDF y el código malicioso.

PDFtk: The PDF Toolkit

De la mano de PDF Labs, tenemos una excelente herramienta para trabajar con ficheros PDFs llamada pdftk y que la mayoría de distribuciones GNU/Linux la incluyen en sus paquetes. La teneis disponible para su descarga en Windows y Mac OS X

Vamos a emplear pdftk para conseguir ver el texto plano de los streams que incorporan los ficheros PDFs y poder trabajar mejor con ellos. La sintaxis para cualquier SS.OO. es muy sencilla:

pdftk pdf_comprimido.pdf output pdf_descomprimido.pdf uncompress
				

Podeis hacer la prueba con el fichero eighth.pdf (722 bytes) y comprobar como nos muestra el texto plano que habíamos comprimido. ¿Habeis notado algo raro? Lo explicaremos en siguientes entregas... De momento, probar con cualquier fichero PDF que tengais a mano...

Dicha herramienta de Sid Steward, la usaremos con frecuencia en el resto de entregas y conviene mirar su página de ayuda para ver la potencia que tiene. Podemos emplearla entre otros usos para volcar todo el texto a un fichero ASCII, reparar la longitud de streams y tabla de referencias cruzadas, etc...

JavaScript y el formato PDF

Mediante JavaScript tenemos una de las formas más fáciles y más potentes de enriquecer el contenido de los ficheros PDFs. Basado en la versión 1.5 de JavaScript ISO-16262 (denominado formalmente como ECMAScript) implementa objetos, métodos y propiedades que nos permiten manipular los ficheros PDFs, modificar su apariencia y muchas otras cosas. Sin embargo, el malo también pensó en aprovecharse del JavaScript para sus cometidos.

Teneis más información sobre JavaScript for Adobe en su página oficial de Adobe.

Vamos a crear el Ejemplo 9 e ir comentado cada novedad que podamos tener para poder incluir código en JavaScript. La "novedad" reside en que debemos incluir un identificador /OpenAction con una referencia indirecta a donde pongamos el código JavaScript en el objeto Catálogo. Por tanto, quedaría de la forma:

1 0 obj
<</Type /Catalog
/Pages 2 0 R
/OpenAction 6 0 R
>>
endobj				
				

Conforme observamos, hacemos referencia al nuevo objeto 6 0 donde vamos a incluir nuestro código JavaScript:

6 0 obj
<<
/Type /Action
/S /JavaScript
/JS (app.alert({cMsg: 'Hola MADESYP!!!', cTitle: 'JavaScript en un PDF', nIcon: 3});)
>>
endobj				
				

Observamos un tag /Type con el /Action para indicarle que se trata de una acción a ejecutar con /S del diccionario /JavaScript. Lo más importante es el código en JavaScript embebido tras el tag /JS, en éste caso empleando el objeto app y el método alert.

Con haber añadido tan sólo un objeto más, ya disponemos de código en JavaScript. Añadiremos el nuevo objeto en la tabla y el trailer, recalcularemos ahora la tabla de referencias cruzadas y el startxref. Para evitaros hacerlo todo, podeis descargar el ejemplo 9 ya completo. Pero no dejeis de abrirlo en un editor de textos para que podais ver toda su estructura ya que en la siguiente entrada, veremos cómo podemos realizarlo de otra forma distinta que nos permitirá en un futuro, ofuscar nuestro código.

Nuestra "manera"

Vamos a crear desde el principio el Ejemplo 10 comparándolo con la forma anterior que hemos hecho. Podeis descargar la plantilla que hemos preparado para poder hacer PDFs más rápidos.

En el objeto Catálogo, vemos que desaparece /OpenAction y ponemos un identificador /Names con otra referencia indirecta. Esto nos permitirá poder apuntar a otras partes del documento en un futuro y jugar un poco al despiste con herramientas automatizadas de búsqueda de código malicioso:

1 0 obj
<</Type /Catalog
/Pages 2 0 R
/Names 6 0 R
>>
endobj					
				

Ahora, vamos a incluir un nuevo objeto con el tag /JavaScript y una referencia indirecta al siguiente objeto:

6 0 obj
<<
/JavaScript 7 0 R
>>
endobj				
				

El siguiente objeto que vamos a crear, nos permitirá nombrar el código JavaScript que luego incluiremos y que tendremos que declarar con su referencia indirecta a donde se encuentra en el fichero PDF:

7 0 obj
<<
/Names [(Codigo Bueno) 8 0 R]
>>
endobj				
				

Y por fin, tendremos en el siguiente objeto el código JavaScript que vamos a emplear en el documento:

8 0 obj
<<
/JS (app.alert({cMsg: "Hola MADESYP!!!", cTitle: "Mensaje emergente"});)
/S /JavaScript
>>
endobj							
				

Una vez que tengamos todos los objetos, indicaremos el número de objetos en la tabla xref, recalcularemos la tabla de referencias cruzadas y el startxref. Vamos a copiar (ojo a los finales de línea, espacios, etc...) o descargar una plantilla para poder trabajar con JavaScript que incluye los 8 objetos que hemos descrito con una tabla de referencias cruzada que referencia a cada uno de los objetos por su número y que nos permitirá posteriormente recalcular tan sólo el último objeto y su comienzo:

%PDF-v1.7
%éééé
% Mi primer PDF con JavaScript hecho a mano por Academia MADESYP

1 0 obj
<</Type /Catalog
/Pages 2 0 R
/Names 6 0 R
>>
endobj

2 0 obj
<</Type /Pages
/Kids [ 3 0 R ]
/Count 1
>>
endobj

3 0 obj
<</Type /Page
/Parent 2 0 R
/MediaBox [ 0 0 595 842 ]
/Resources << /Font << /F1 5 0 R >> >>
/Contents 4 0 R
>>
endobj

4 0 obj
<< /Length 56 >>
stream
BT /F1 50 Tf 100 790 Td (Hola MADESYP!!!) Tj ET
endstream
endobj

5 0 obj
<<
/Type /Font
/Subtype /Type1
/BaseFont /Arial
>>
endobj

6 0 obj
<<
/JavaScript 7 0 R
>>
endobj

7 0 obj
<<
/Names [(Codigo Bueno) 8 0 R]
>>
endobj

8 0 obj
<<
/JS (app.alert({cMsg: "Hola MADESYP!!!", cTitle: "Mensaje emergente"});)
/S /JavaScript
>>
endobj

xref
0 9
0000000000 65535 f
0000000001 00000 n
0000000002 00000 n
0000000003 00000 n
0000000004 00000 n
0000000005 00000 n
0000000006 00000 n
0000000007 00000 n
0000000008 00000 n

trailer
<</Size 9
/Root 1 0 R
>>

startxref
123
%%EOF				
				

Cuando lo editamos para saber los offsets de los objetos con hexedit, debemos cambiar la tabla de referencias cruzadas por la siguiente así como su comienzo:

[...]

xref
0 9
0000000000 65535 f
0000000086 00000 n
0000000154 00000 n
0000000220 00000 n
0000000357 00000 n
0000000462 00000 n
0000000537 00000 n
0000000583 00000 n
0000000640 00000 n

trailer
<</Size 9
/Root 1 0 R
>>

startxref
757
%%EOF				
				

El resultado final, podeis descargarlo de nuestro repositorio maligno. En el blog de Didier Stevens teneis unas excelentes herramientas que os permitirán añadir otros códigos en JavaScript y ficheros embebidos.

Para poder ver un fichero PDFs con código más complejo en JavaScript y que tendreis que descomprimir para poder estudiarlo, os dejamos una muestra de Planet PDF de un validador de tarjetas bancarias para que podais analizar su código y estructura.

Y para ver pequeños "trucos" con los ficheros, nada mejor que un recopilatorio sobre muchas cosas relacionadas con el formato de los ficheros. Por ejemplo, en cuando a la codificación basada en JavaScript con concatenación, reemplazar, secuencias de escape, codificación en otras bases, etc... e incluso, pruebas para detectar su ejecución en emuladores y herramientas de análisis de código malicioso.

En la siguiente entrega...

Aunque podríamos entrar en cómo mostrar gráficos, formas básicas, etc..., como eso podeis hacerlo perfectamente con la documentación oficial, nos dedicaremos a las marcas de agua, contraseñas y principalmente dedicaremos casi toda la entrega a "jugar con paletas de grises indizadas" que nos permitirán incluir bloques colisionados para poder generar PDFs con el mismo resúmen MD5 y diferente información en su interior. No olvideis que todo se basa en PRACTICAR, PRACTICAR y PRACTICAR...

Recordaros que en los Cursos especializados de Seguridad Informática y Administración de Sistemas que ofrecemos en Academia MADESYP realizamos y establecemos las contramedidas con todo esto y mucho más...

Ser buenos y no hagáis maldades!