Maltego Transform Development Primer

28 April 2015

For the uninitiated, Maltego is an awesome Open-Source Intelligence (OSINT) tool to identify links and relationships between entities in a clean, visual way. With Maltego, you can run transforms on entities (An entity is any item in your Maltego graph) in your graph which use entity you select as input and produce a new output based on the transform.

These transforms can do something as simple as doing a WHOIS query on an IP and returning the Admin email address, to something like, taking a file hash and returning all the various signatures it identified from VirusTotal. The great thing about these transforms is, if you can write simple Python scripts, you can write a transform.


To start, you'll need to get a copy of Maltego...obviously. Next, you'll want to grab the modules developed by Paterva. This contains all the necessary code we'll need when we write our transform.

Place the script into a new directory; let's call it vt_transform. Next create a new file in the same directory called

There are a few minor differences when using Maltego on Windows vs. UNIX machines, but, the code will remain the same, you'll just need to set up the transforms a little bit differently, but, I'll cover that when we get to that part.

Next, you'll need to install the Requests Python library. If you're using pip, you can install it by pip install requests, which couldn't be easier and the method I would recommend.

Lastly, you'll need to grab an API key from VirusTotal. This is a really easy process, you'll just need to make an account, and once logged in, you can click on your name in the top-right corner, which will give you a dropdown menu with an option for your API key.

Writing the Code

Today, we'll be writing a simple transform, which will use VirusTotal to lookup an IP Address and return any domains it has resolved to in the past. This is great for researching possibly malicious IP addresses to see what other domains this IP resolved to, which can provide you more indicators to look for, as well as being able to map out more of the adversary's infrastructure.

Open up your favorite text editor and open up the file you created Let's start by importing some modules we'll be needing:

from MaltegoTransform import *
import requests
import json
import sys

The first line is to import the MaltegoTransform module you downloaded previously and contains all the functions we'll need to generate a new entity. Next, we're importing the requests Python library, which will handle interacting with the VirusTotal API. Next, we need to import the JSON library, which will help us parse through the JSON response that gets returned by the VirusTotal API. Lastly, we are importing the sys library, which will give us access to the arguments supplied to our script.

Next up in the script, let's enter a few variables (Type the code in, and then I'll explain what each one is):


url = ""
params = {
    "apikey":"<insert your API key here>"

me = MaltegoTransform()

Above, we are first getting the IP Address of the entity we want to run the transform on, by getting the item stored in the 1st argument by using ip = sys.argv[1]. This is saying IP equals whatever text is in the "1st" argument, since the transform name will be argv[0], we'll need to use argv[1], which is the IP Address we select in Maltego.

Next we're setting up a variable, which is the URL to virustotal's API for IP Address reports. This was obtained by the documentation provided by VirusTotal, which is awesome btw. We don't have to do this, but, it's a lot easier to manage the requests later on by doing so, plus, if we need to reuse this variable, it makes life easier as well.

Lastly, we need to define the parameters we are going to send along in our request to VirusTotal. This is using a Python dictionary, where each key has it's own value. In our example, "ip" is equal to the ip variable we specified earlier, which is essentially whatever entity we are running the transform on. With every request we make to VirusTotal, we need to supply our API key, which is what we're doing here. Where I put <insert your API key here>, you'll need to replace everything in between the quotes with your own key.

One more variable set up is me = MaltegoTransform() which is setting me to the MaltegoTransform function found in the Python library we grabbed earlier.

Now that we have set up a few variables, we are now ready to make the actual request to VirusTotal to get our information:

Again, type this part in your script, and then I'll explain what we're doing:

r = requests.get(url, params=params, verify=False)
j = json.loads(r.text)

This is setting up requests to use the url variable (which was for the VirusTotal API) and then having the params variable set to the parameters we entered earlier. Lastly, the verify=False is for SSL certificate verification, which I think was giving me errors, so I just disabled it for the time being.

Next, we set j to equal the JSON format of r.text, which is the text returned from our initial request. There's also other content that can be returned, like r.headers which return the headers from your request, or r.status_code, which returns the status code of your request (200,404,503, etc.).

Lastly, we'll loop through the returned JSON to pull out all the domains this IP was seen resolving to. Go ahead and type this code block in:

for url in j['detected_urls']:
	me = MaltegoTransform()
	ent = me.addEntity("maltego.Domain",url['url'])


Before we get into the above code block, it might help to see what gets retrieved with r.text:

{u'last_resolved':  u'2014-03-07 00:00:00', 
u'hostname': u''}, 
{u'last_resolved': u'2014-09-17 00:00:00', 
u'hostname': u''}], 
	[{u'url': u'', 
		u'positives': 2, 
		u'total': 52, 
		u'scan_date': u'2014-04-06 11:18:17'}, 
	{u'url': u'', 
		u'positives': 3, 
		u'total': 52, 
		u'scan_date': u'2014-03-05 09:13:31'}], 
u'asn': u'25532'}

The above is a snippet of the JSON in r.text. The important part in here is the detected_urls portion, since this contains each URL this IP resolved to in the past. Looking again at our code block, the first part is setting up a loop to go through each url in detected_urls. For each url we see in that JSON section, we set ent to equal me (or MaltegoTransform()) and use the addEntity function.

We set a few arguments for this function, with the first one being what type of entity we want to return. In our case, we are setting the entity type to maltego.Domain. Maltego ships with tons of entities already made, "domain" being one of them. Next, we are setting the display text of the entity to equal the value of the url key in the JSON text.

Lastly, we call the me.returnOutput() function which will return an XML formatted text block of our entity which Maltego can parse and use to display entities. With that being said, you could even just call a Python script which will return a wall of XML text (formatted so Maltego can use it) and Maltego can add or edit the entity based off that, this function just takes that hard/manual work and does it for you. Here's an example of the XML that Maltego will be able to parse and create entities based off of:

		<Entity Type="maltego.Phrase">
			<Value>Hello Transform World</Value>

That's it! You just wrote your very own transform for Maltego. Next, we'll need to save it to the directory vt_transform we set up earlier, so we can import it into Maltego and start using it.

Configuring Maltego

Now that we got the hard part of the way, let's fire up Maltego and configure it to use the newly created transform.

Once started, click on "Manage", then "Local Transform". In the dialog box, go through and fill them out like so:

  • Give your new transform a name, like " VT - IP to Domains"
  • Fill in your description, like "VirusTotal IP Lookup".
  • Next, give your transform an Transform ID, which is usually prepended with your current username, however, you can change this, something like "vt.iptodomain" will work.
  • Enter your name in "Author"
  • Input Entity Type should be "IPv4 Address"
  • Transform Set can be set to none, since this isn't part of any set right now.
  • Click "Next"

Now you need to tell Maltego where to find your transform:

  • Command should be set to "/usr/bin/python"
  • Parameters will be ""
  • Working Directory should be set to "/vt_transform" or wherever you said the script.
  • Click Finish

That's it! Now, drag a new IPv4 Address entity into a new Maltego graph and right-click on it. Click on "Run Transform" > "All Transforms" > "VT - IP to Domains". Hopefully it'll run smoothly and if VirusTotal had seen this IP previously, it should create new domain entities for each domain it had previously resolved to.


It may seem like a lot of work to create a transform, but, once you create one or two, the process will go by a lot quicker and you crank them out really quick.

Hopefully this will show you the possibilities of Maltego and the types of things you can create, but the possibilities are really limitless.

If you want some motivation, here are some transforms I created in the past:

Lastly, there is one other thing I want to mention about transforms, there is a project called Canari which makes developing transforms a lot easier. I've never gotten into it, just because I learned how to write them using the way I wrote my blog post about, however, it could make the process easier for others as I've heard tons of good things about it.