Example 2: ASN TDS Transform

Modified on: Tue, 21 Jul, 2020 at 12:13 PM

Let’s say, for example, that you want to create a Transform that runs on a Phrase Entity. The Transform will assume that the phrase is a number and it will return as many Entities – and just as a test – we’ll return these as number Entities.


We call it ‘EnumAS’ on the TDS and point it to a URL http://server:8080/EnumAS. If you used Apache as a server you will need to restart the server to register the changes (/etc/init.d/apache2 reload). If you used the bottle server and it is running, no changes should be needed.


Here is the output:



Limiting Results


The Maltego user might want to only return a limited number of results (or his license may limit how many he is allowed). Even though Maltego will discard any extra results, you might want to decide which results should be kept. Furthermore, by limiting the number of results on the server, you can optimize database lookups and reduce the network traffic back to the user.


Change the code to read the slider to ensure that the number of Entities returned is in line with those requested by the user.


@classmethod
def create_entities(cls, request, response):
    howmany = int(request.Value)
    if (howmany > request.Slider):
        howmany = request.Slider

    for i in range(1,howmany+1):
        response.addEntity('maltego.AS', str(i))


Warnings and Errors

Next, we want to make sure it’s a number and if not, to return a message to the user along the lines that he/she is not understanding the Transform. We add a check at the start of the method and return a message on the error:


@classmethod
def create_entities(cls, request, response):
    if (not request.Value.isdigit()):
        response.addUIMessage("Sorry but [" + request.Value + "] is not a whole number", 
          UIM_TYPES["partial"])

    else:
        # here we know we're good to go.
        howmany = int(request.Value)
...


Now when we run the Transform on an Entity that is not a number, we will see the following in the Transform output window within Maltego:



Entity Weights

Notice how the Entities seem to be laid out on the graph randomly? This is because Maltego lays out Entities according to their weight (top left to bottom right). Since we did not set the weight all the Entities have the same weight – the default of 100. This can be fixed by making the following changes:


...
        for i in range(1, howmany + 1):
            # response.addEntity adds an entity to the return vessel with type 'Type' 
            # and value 'Value'.
            Ent = response.addEntity('maltego.AS', str(i))
            Ent.setWeight(howmany-1)
...


Here you see that the addEntity method returns a MaltegoEntity object which we can modify. In this case, we use it to set the weight and the graph returned now looks as follows:



The first node (with value ‘1’) has a weight of 19 and the last node (’20’) has a weight of 0. You can see this in the Property View. The following screenshot shows the property view when the AS node with value ‘1’ was selected:



Using Display Info

To create some HTML in the Detail View to go with our AS Entities. The ‘addDisplayInformation’ method is used. The content could be plain text or HTML. Let’s start with a simple request to display the words “This is number X” on each Entity returned. We want this to be rendered in a label called ‘AS Number’. Here is how the script now appears:


@classmethod
def create_entities(cls, request, response):
    if (not request.Value.isdigit()):
        response.addUIMessage("Sorry but [" + request.Value + "] is not a whole number", 
        UIM_TYPES["partial"])

        else:
            # here we know we're good to go.
            howmany = int(request.Value)

            if (howmany > request.Slider):
                # request.Slider is the slider's value - e.g. how many results should be 
                # returned.
                howmany = request.Slider
        
            for i in range(1, howmany + 1):
                # response.addEntity adds an entity to the return vessel with type 'Type' 
                # and value 'Value'.
                Ent = response.addEntity('maltego.AS', str(i))
                Ent.setWeight(howmany-1)
                Ent.addDisplayInformation("This is number " + str(i), "AS Number")


When you run the Transform now, you’ll see that each node rendered the label and content:



You can easily add more labels. Display information is merged when other Transforms create the same Entity instance. Maltego will expand the label by default, but Detail View labels can be collapsed and expanded by the user and the client will remember the choice for future operations.


For HTML, let’s assume we want to make a hyperlink. We will create the link to be dynamic and based on the node number. Consider the following code:


@classmethod
def create_entities(cls, request, response):
    if (not request.Value.isdigit()):
        response.addUIMessage("Sorry but [" + request.Value + "] is not a whole number", 
          UIM_TYPES["partial"])

    else:
        # here we know we're good to go.
        howmany = int(request.Value)

        if (howmany > request.Slider):
            howmany = request.Slider
    
        for i in range(1, howmany + 1):
            Ent = response.addEntity('maltego.AS', str(i))
            Ent.setWeight(howmany-1)
            Ent.addDisplayInformation("This is number " + str(i), "AS Number")
            Ent.addDisplayInformation('Click <a \
              href="https://www.ultratools.com/tools/asnInfoResult?domainName=AS' + str(i) 
              + '"> here </a> to get more information about this AS', "AS Info Link")


Each node now has a link that will open the browser to a specific webpage:



Clicking on the links gives us:



It’s important to note that display information is never sent to the server (when you run subsequent Transforms on the node) – it is read-only info and is kept in the client.


Note: The Display Information is never sent to the server when you run subsequent Transforms on the node—it is read-only information and is kept in the client.


Note: If the same Entity is returned multiple times from a transform, Maltego will automatically merge them. If these Entities have different Display Information, they will be concatenated.

Link properties, bookmarks, and notes

You can set the link color, label, width, breadth, and style easily. Consider this code snippet (used in the same Transform). The result is apparent in the code and the resultant graph:


if (i%2 == 0):
    Ent.setLinkColor('0x00FF00')
    Ent.setNote('Even')
    Ent.setLinkLabel('Even link')
    Ent.setLinkStyle(LINK_STYLES["normal"])
    Ent.setLinkThickness(1)
    Ent.setBookmark(BOOKMARK_CLRS["green"])
else:
    Ent.setLinkColor('0xFF0000')
    Ent.setNote('Odd')
    Ent.setLinkLabel('Odd link')
    Ent.setLinkStyle(LINK_STYLES["dashed"])
    Ent.setLinkThickness(2)
    Ent.setBookmark(BOOKMARK_CLRS["red"])


This code illustrates the concept through the graph returned, shown below, is untidy in an organic layout.



Keep in mind that Entity notes stack on top of each other, this means that when a subsequent Transform writes to a note it will append to the note rather than deleting the previous one.


Transform Settings

Let’s add a Transform setting that will obtain a number prior to the Transform running and only return AS numbers that are divisible by that setting. Firstly, we need to define a Transform setting on the TDS. Navigate to the root, click on Settings, and click Add setting. You will be required to fill out the following form:



Our variable will be called ISDIV. Once you’ve added the Transform setting click on Add Transform Setting. Navigate to Transforms, click on the EnumAS Transform (created at the start of this document) and scroll down. In the Transform Settings window highlight the ‘ISDIV’ item:



This tells the TDS that you wish to use the ISDIV Transform setting in your ransform. Transforms can have multiple settings and the same settings can be used on multiple Transforms.


We will need to refresh our code:


def create_entities(cls, request, response):
    if (not request.Value.isdigit()):
        response.addUIMessage("Sorry but [" + request.Value + "] is not a whole number", 
          UIM_TYPES["partial"])

    else:
        isdiv = request.getTransformSetting('ISDIV')

        if (not isdiv.isdigit()):
            response.addUIMessage('Silly! We need a number',UIM_TYPES["fatal"])

        else:
            howmany = int(request.Value)
            accum = 0

            for i in range(1, howmany + 1):

                if (i % int(isdiv) == 0):
                    Ent =response.addEntity('maltego.AS', str(i))
                    Ent.setWeight( howmany - 1 )
                    accum = accum + 1
                    if (accum >= request.Slider):
                        break   


Now, when we run the Transform, we get a pop-up requesting some input:



In code, we add a check and an error message when the value is not a number:


isdiv = request.getTransformSetting('ISDIV')

if (not isdiv.isdigit()):
    response.addUIMessage('Silly! We need a number',UIM_TYPES["fatal"])


This checks if the value is indeed a digit – if it’s not, an error message is shown (UIM_FATAL, or UIM_TYPES[“fatal”]):



It’s important to remember that the TDS only supports strings. As such we need to convert our string to an integer and make sure that we return the right number of nodes back to the user.


The output of the Transform is as expected:


Did you find it helpful? Yes No

Send feedback
Sorry we couldn't be helpful. Help us improve this article with your feedback.