Introducción al desarrollo de transforms para Maltego

Una de las características más interesantes de Maltego, herramienta ya potente de por sí, es la posibilidad de desarrollar nuestras propias transformadas, o transforms, para ampliar sus capacidades.

De forma simplificada, una transform no es más que una «caja negra» que toma como entrada una entidad (o entity), y produce como salida una o más entities. Estas entities, ya sean de entrada o de salida, son de cierto tipo (por ejemplo, de tipo Person, EmailAddress, etc.) y deben tener un valor (o value) determinado. Adicionalmente, una entidad puede tener cero o más campos (o fields) extra que pueden enriquecer su valor semántico (cada field está formado por un nombre y un valor).

Las transforms pueden ser locales o remotas: las primeras se ejecutan en la misma máquina donde corre el propio programa cliente de Maltego, mientras que las segundas residen en los llamados servidores TDS (Transform Distribution Service), de forma que el programa cliente de Maltego envía la entity de entrada y recibe de los servidores la entity o entities de salida.

Centrándonos ya en la programación en sí de las transformadas, objeto de la presente entrada, el interfaz para el desarrollo de las transforms locales es sumamente sencillo: cualquier programa que reciba como parámetros el value de la entity de entrada (y, opcionalmente, los nombres y valores de los fields extra), y genere código XML en el formato de especificación de las entities de salida (con sus respectivos valores, etc.), puede registrarse en el programa cliente de Maltego y funcionar como una transform más.

El típico ejemplo es una transform que recibe como entrada una entity de tipo Person (con el value fijado al nombre de una persona), y genera como su salida una entity de tipo Phrase, con su value fijado al valor «Hola PERSONA«. En Python (aunque, obviamente, este paradigma es independiente del lenguaje de programación utilizado), bastaría con el siguiente código para crear esta sencilla transform:

blog@s2grupo:/home/blog$ cat saludo.py

#!/usr/bin/env python

import sys

name = sys.argv[1]

print """
    <MaltegoMessage>
    <MaltegoTransformResponseMessage>
    <Entities>
    <Entity Type="Phrase"><Value>Hola %s</Value></Entity>
    </Entities>
    </MaltegoTransformResponseMessage>
    </MaltegoMessage>
""" % name

Como se observa, es necesario indicar el tipo de la entity de salida (Phrase), expresada en XML, pero no se indica el tipo de la de entrada (Person), que llega directamente a través del primer parámetro.

blog@s2grupo:/home/blog$ ./saludo.py Pepito

    <MaltegoMessage>
    <MaltegoTransformResponseMessage>
    <Entities>
    <Entity Type="Phrase"><Value>Hola Pepito</Entity>
    </Entities>
    </MaltegoTransformResponseMessage>
    </MaltegoMessage>

A continuación, para importarla en el cliente de Maltego, pulsamos el botón Local Transform:

Indicamos un nombre para la transformada, así como un ID, y (aquí sí) el tipo de la entity de entrada: Person.

Por último, indicamos el programa a ejecutar y la ruta de trabajo. Como se observa, de existir fields, se indicarían como segundo parámetro con el formato field1=valor del field1#field2=valor del field2 … (a los compañeros de S2 les suena este formato de algo? ;-)

Una vez importada, si creamos en un nuevo grafo de trabajo una Person de ejemplo (value John Doe):

Podremos aplicar la nueva transform, que generará la correspondiente Phrase:

Éste es el aspecto que presenta la nueva transformada en el Transform Manager:

Como se ha visto, es relativamente sencillo programar nuestras propias transforms locales, pero este modo de trabajar presenta algunas limitaciones, como por ejemplo:

  • La generación del código XML de este modo es algo engorrosa, y este hecho se agrava cuando empezamos a generar varias entities de salida, y además éstas presentan fields además de value
  • Si tenemos un conjunto de transforms, es muy trabajoso tener que importarlas en el cliente de Maltego una a una
  • A la hora de distribuir los programas que conforman las transforms, ¿cómo distribuimos también sus dependencias? ¿Qué estructura o convenciones podríamos seguir?
  • Si en un momento dado nos interesa distribuir una transform local a un servidor TDS, para convertirla en una transformada remota, la especificación es distinta, y no podríamos reutilizar el código de forma directa

Para superar las anteriores limitaciones, surgieron (y siguen surgiendo) gran cantidad de librerías para diferentes lenguajes de programación. Una de las más completas, actualizadas y mantenidas, hoy por hoy, para el lenguaje Python, es Canari Framework (sobre la que se basan a su vez otros muchos conjuntos de transformadas, como el popular Sploitego. Canari nos ofrece soluciones a todas las anteriores cuestiones (y mucho más).

Su instalación es relativamente sencilla: en primer lugar, necesitaremos los paquetes de desarrollo para Python:

blog@s2grupo:/home/blog$ sudo apt-get install python-dev

A continuación, podemos instalar Canari y todas sus dependencias en un virtualenv de Python:

blog@s2grupo:/home/blog$ virtualenv canari
New python executable in canari/bin/python
Installing setuptools, pip...done.

blog@s2grupo:/home/blog$ cd canari/
blog@s2grupo:/home/blog/canari$ . bin/activate

blog@s2grupo:/home/blog/canari$ easy_install canari
Searching for canari
Best match: canari 1.1
Adding canari 1.1 to easy-install.pth file
[...]
Finished processing dependencies for canari

blog@s2grupo:/home/blog/canari$ python 
>>> import canari
>>> 

Una vez instalada, tendremos disponible el comando canari dentro del virtualenv. Con el argumento create-package se genera el esqueleto para un nuevo paquete de reglas, que hemos llamado testpackage:

blog@s2grupo:/home/blog/canari$ canari create-package testpackage
Welcome to the Canari transform package wizard.
Would you like to specify authorship information? [Y/n]: n
creating skeleton in testpackage
creating directory testpackage
creating directory testpackage/src
creating directory testpackage/maltego

Y para crear una nueva transform, llamada saludo, dentro de este paquete de reglas, ejecutamos create-transform dentro del subdirectorio src del directorio del paquete de reglas (testpackage):

blog@s2grupo:/home/blog/canari/testpackage/src$ canari create-transform saludo
creating file /home/blog/canari/testpackage/src/testpackage/transforms/saludo.py...
updating /home/blog/canari/testpackage/src/testpackage/transforms/__init__.py
done!

El esqueleto para la nueva transform se genera en testpackage/transforms/saludo.py. Adaptándolo a la funcionalidad deseada (la transform será equivalente a la que creamos al principio), el código quedaría:

#!/usr/bin/env python

from canari.maltego.utils import debug, progress
from canari.framework import configure
from canari.maltego.entities import Person

@configure(
    label='To MyTestpackageEntity [Saludo]',
    description='Saludo',
    uuids=[ 'testpackage.v2.MyTestpackageEntityToPhrase_Saludo' ],
    inputs=[ ( 'Testpackage', Person ) ],
    debug=True
)
def dotransform(request, response, config):
    response += Person("Hola %s" % request.value)
    return response

Como se observa, ya no es necesario especificar código XML: la transform pasa a ser tan sólo una función, dotransform(), que toma la entrada en su parámetro request, y la referencia a la respuesta que tiene que generar en el parámetro reponse. Las entities pasan a especificarse directamente con objetos (en este caso un objeto Person), que han de importarse desde los módulos del framework. Para que Canari pueda importar automáticamente el paquete de transforms en el cliente de Maltego, es necesario especificar una serie de metadatos con ayuda del decorator configure:

  • label: menú contextual del cliente de Maltego donde será accesible la nueva transform (una vez seleccionas la entity de entrada y pulsas botón derecho, Run Transform)
  • description: la descripción correspondiente
  • uuids: el ID de la transformada
  • inputs: el tipo de la entity de entrada
  • debug (opcional): si queremos contar o no con información de depuración

Contando con esta información, el propio Canari ya puede generarnos un fichero .mtz con todas las transformadas contenidas en el paquete para importarlas de golpe en el cliente Maltego, sin necesidad de pasar por asistentes ni similares, utilizando la opción create-profile:

blog@s2grupo:/home/blog/canari/testpackage/src$ canari create-profile testpackage
Looking for transforms in testpackage.transforms...
Package loaded.
Creating profile /home/blog/canari/testpackage/src/testpackage.mtz...
Installing transform testpackage.v2.MyTestpackageEntityToPhrase_HelloWorld from 
testpackage.transforms.helloworld...
Writing /home/blog/canari/local/lib/python2.7/site-packages/canari/resources/template/canari.plate 
to /home/blog/canari/testpackage/src/canari.conf
Writing testpackage/resources/etc/testpackage.conf to /home/blog/canari/testpackage/src/testpackage.conf
Writing transform set Testpackage to /home/blog/canari/testpackage/src/testpackage.mtz...
Writing server Local to /home/blog/canari/testpackage/src/testpackage.mtz

%%%%%%%%%%%%%%%%%%%%%%%%%%% SUCCESS! %%%%%%%%%%%%%%%%%%%%%%%%%%%

 Successfully created /home/blog/canari/testpackage/src/testpackage.mtz. You may now import this file into
 Maltego.

 INSTRUCTIONS:
 -------------
 1. Open Maltego.
 2. Click on the home button (Maltego icon, top-left corner).
 3. Click on 'Import'.
 4. Click on 'Import Configuration'.
 5. Follow prompts.
 6. Enjoy!

%%%%%%%%%%%%%%%%%%%%%%%%%%% SUCCESS! %%%%%%%%%%%%%%%%%%%%%%%%%%%





Como se observa, la transformada ya funciona: además de no tener que importar una a una a mano, no ha sido necesario especificar los tipos de las entities de entrada, ni los comandos necesarios para ejecutar las transformadas:

Simplemente cambiando el código Python, la transformada puede pasar a devolver más de una entity de salida, sin escribir una sola línea de XML:

blog@s2grupo:/home/blog/canari/testpackage/src$ cat testpackage/transforms/saludo.py
#!/usr/bin/env python

from canari.maltego.utils import debug, progress
from canari.framework import configure
from canari.maltego.entities import Person

@configure(
    label='To MyTestpackageEntity [Saludo]',
    description='Saludo',
    uuids=[ 'testpackage.v2.MyTestpackageEntityToPhrase_Saludo' ],
    inputs=[ ( 'Testpackage', Person ) ],
    debug=True
)
def dotransform(request, response, config):
    i = 0
    for i in range(50):
        response += Person("Hola %s %d" % (request.value, i))
    return response

Además, nuestras transformadas están recogidas en un paquete Python, que puede redistribuirse (junto con la información acerca de todas las dependencias necesarias para la ejecución de nuestro código), con las herramientas estándar del lenguaje. Si deseáramos convertir la transformada local en una transformada remota, bastaría con cambiar un par de líneas de código.

Esperamos que la entrada haya sido de vuestro interés. ¡Hasta la próxima!

Referencias e información adicional

Developer: Local Transforms.
Local Transform Specification (PDF).
Specification – Paterva Wiki.
Transform Development Quick Start.