├── CodeblocksToHash.py ├── HashToCodeblocks.py ├── MaltegoTransform.py ├── Media ├── Block_length.png ├── Codeblock_properties.png └── Example.png ├── README.md └── VTCodeBlocks.mtz /CodeblocksToHash.py: -------------------------------------------------------------------------------- 1 | #Maltego Transfrom - VirusTotal Codeblock to Hash @arieljt 2 | 3 | from MaltegoTransform import * 4 | import requests 5 | import json 6 | 7 | apiurl = "https://virustotal.com/api/v3/" 8 | apikey = "" 9 | 10 | mt = MaltegoTransform() 11 | mt.parseArguments(sys.argv) 12 | code_block_id = mt.getVar('properties.BlockID').strip() 13 | 14 | 15 | try: 16 | headers = {'x-apikey': apikey, 'Content-Type': 'application/json'} 17 | response = requests.get(apiurl + 'intelligence/search?query=code-block:' + code_block_id, headers=headers) 18 | response_json = response.json() 19 | 20 | for item in response_json['data']: 21 | me = mt.addEntity("maltego.Hash", '%s' % item['attributes']['md5'].encode("ascii")) 22 | me.setLinkStyle(LINK_STYLE_DASHED) 23 | 24 | except: 25 | mt.addUIMessage("Exception Occurred") 26 | 27 | 28 | mt.returnOutput() 29 | -------------------------------------------------------------------------------- /HashToCodeblocks.py: -------------------------------------------------------------------------------- 1 | #Maltego Transfrom - VirusTotal Hash to Codeblocks @arieljt 2 | 3 | from MaltegoTransform import * 4 | import requests 5 | import json 6 | 7 | apiurl = "https://virustotal.com/api/v3/" 8 | apikey = "" 9 | 10 | mt = MaltegoTransform() 11 | mt.parseArguments(sys.argv) 12 | file_hash = mt.getVar('properties.hash').strip() 13 | 14 | 15 | try: 16 | headers = {'x-apikey': apikey, 'Content-Type': 'application/json'} 17 | response = requests.get(apiurl + 'intelligence/search?query=code-similar-to:' + file_hash, headers=headers) 18 | response_json = response.json() 19 | 20 | for item in response_json['data']: 21 | if item['attributes']['md5'] == file_hash: 22 | for block in item['context_attributes']['code_block']: 23 | if block['length'] >= 5: # Adjust the minimal Codeblock length requirement 24 | me = mt.addEntity("arieljt.VTCodeblock", '%s' % block['binary']) 25 | me.addAdditionalFields("properties.BlockID", "Block ID", 'false', '%s' % block['id']) 26 | me.addAdditionalFields("properties.BlockLength", "Block Length", 'false', '%d' % block['length']) 27 | me.setLinkLabel("Offset %s" % block['offset']) 28 | except: 29 | mt.addUIMessage("Exception Occurred") 30 | 31 | 32 | mt.returnOutput() 33 | -------------------------------------------------------------------------------- /MaltegoTransform.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | ####################################################### 3 | # Maltego Python Local Transform Helper # 4 | # Version 0.3 # 5 | # # 6 | # Local transform specification can be found at: # 7 | # http://ctas.paterva.com/view/Specification # 8 | # # 9 | # For more help and other local transforms # 10 | # try the forum or mail me: # 11 | # # 12 | # http://www.paterva.com/forum # 13 | # # 14 | # Andrew MacPherson [ andrew <> Paterva.com ] # 15 | # # 16 | # UPDATED: 2016/08/29 - PR ["&",">","<"] # 17 | # UPDATED: 2015/10/29 - PR # 18 | # UPDATED: 2016/08/19 - AM (fixed # and = split) # 19 | ####################################################### 20 | 21 | import sys 22 | 23 | 24 | BOOKMARK_COLOR_NONE="-1" 25 | BOOKMARK_COLOR_BLUE="0" 26 | BOOKMARK_COLOR_GREEN="1" 27 | BOOKMARK_COLOR_YELLOW="2" 28 | BOOKMARK_COLOR_ORANGE="3" 29 | BOOKMARK_COLOR_RED="4" 30 | 31 | LINK_STYLE_NORMAL="0" 32 | LINK_STYLE_DASHED="1" 33 | LINK_STYLE_DOTTED="2" 34 | LINK_STYLE_DASHDOT="3" 35 | 36 | UIM_FATAL='FatalError' 37 | UIM_PARTIAL='PartialError' 38 | UIM_INFORM='Inform' 39 | UIM_DEBUG='Debug' 40 | 41 | 42 | class MaltegoEntity(object): 43 | value = "" 44 | weight = 100 45 | displayInformation = None 46 | additionalFields = [] 47 | iconURL = "" 48 | entityType = "Phrase" 49 | 50 | def __init__(self,eT=None,v=None): 51 | if (eT is not None): 52 | self.entityType = eT 53 | if (v is not None): 54 | self.value = sanitise(v) 55 | self.additionalFields = [] 56 | self.displayInformation = None 57 | 58 | def setType(self,eT=None): 59 | if (eT is not None): 60 | self.entityType = eT 61 | 62 | def setValue(self,eV=None): 63 | if (eV is not None): 64 | self.value = sanitise(eV) 65 | 66 | def setWeight(self,w=None): 67 | if (w is not None): 68 | self.weight = w 69 | 70 | def setDisplayInformation(self,di=None): 71 | if (di is not None): 72 | self.displayInformation = di 73 | 74 | def addAdditionalFields(self,fieldName=None,displayName=None,matchingRule=False,value=None): 75 | self.additionalFields.append([sanitise(fieldName),sanitise(displayName),matchingRule,sanitise(value)]) 76 | 77 | def setIconURL(self,iU=None): 78 | if (iU is not None): 79 | self.iconURL = iU 80 | 81 | def setLinkColor(self,color): 82 | self.addAdditionalFields('link#maltego.link.color','LinkColor','',color) 83 | 84 | def setLinkStyle(self,style): 85 | self.addAdditionalFields('link#maltego.link.style','LinkStyle','',style) 86 | 87 | def setLinkThickness(self,thick): 88 | self.addAdditionalFields('link#maltego.link.thickness','Thickness','',str(thick)) 89 | 90 | def setLinkLabel(self,label): 91 | self.addAdditionalFields('link#maltego.link.label','Label','',label) 92 | 93 | def setBookmark(self,bookmark): 94 | self.addAdditionalFields('bookmark#','Bookmark','',bookmark) 95 | 96 | def setNote(self,note): 97 | self.addAdditionalFields('notes#','Notes','',note) 98 | 99 | def returnEntity(self): 100 | print "" 101 | print "" + str(self.value) + "" 102 | print "" + str(self.weight) + "" 103 | if (self.displayInformation is not None): 104 | print "" 105 | if (len(self.additionalFields) > 0): 106 | print "" 107 | for i in range(len(self.additionalFields)): 108 | if (str(self.additionalFields[i][2]) <> "strict"): 109 | print "" + str(self.additionalFields[i][3]) + "" 110 | else: 111 | print "" + str(self.additionalFields[i][3]) + "" 112 | print "" 113 | if (len(self.iconURL) > 0): 114 | print "" + self.iconURL + "" 115 | print "" 116 | 117 | class MaltegoTransform(object): 118 | entities = [] 119 | exceptions = [] 120 | UIMessages = [] 121 | values = {} 122 | 123 | def __init__(self): 124 | values = {} 125 | value = None 126 | 127 | def parseArguments(self,argv): 128 | if (argv[1] is not None): 129 | self.value = argv[1] 130 | 131 | if (len(argv) > 2): 132 | if (argv[2] is not None): 133 | vars = argv[2].split('#') 134 | for x in range(0,len(vars)): 135 | vars_values = vars[x].split('=',1) 136 | if (len(vars_values) == 2): 137 | self.values[vars_values[0]] = vars_values[1] 138 | 139 | def getValue(self): 140 | if (self.value is not None): 141 | return self.value 142 | 143 | def getVar(self,varName): 144 | if (varName in self.values.keys()): 145 | if (self.values[varName] is not None): 146 | return self.values[varName] 147 | 148 | def addEntity(self,enType,enValue): 149 | me = MaltegoEntity(enType,enValue) 150 | self.addEntityToMessage(me) 151 | return self.entities[len(self.entities)-1] 152 | 153 | def addEntityToMessage(self,maltegoEntity): 154 | self.entities.append(maltegoEntity) 155 | 156 | def addUIMessage(self,message,messageType="Inform"): 157 | self.UIMessages.append([messageType,message]) 158 | 159 | def addException(self,exceptionString): 160 | self.exceptions.append(exceptionString) 161 | 162 | def throwExceptions(self): 163 | print "" 164 | print "" 165 | print "" 166 | 167 | for i in range(len(self.exceptions)): 168 | print "" + self.exceptions[i] + "" 169 | print "" 170 | print "" 171 | print "" 172 | exit() 173 | 174 | def returnOutput(self): 175 | print "" 176 | print "" 177 | 178 | print "" 179 | for i in range(len(self.entities)): 180 | self.entities[i].returnEntity() 181 | print "" 182 | 183 | print "" 184 | for i in range(len(self.UIMessages)): 185 | print "" + self.UIMessages[i][1] + "" 186 | print "" 187 | 188 | print "" 189 | print "" 190 | 191 | def writeSTDERR(self,msg): 192 | sys.stderr.write(str(msg)) 193 | 194 | def heartbeat(self): 195 | self.writeSTDERR("+") 196 | 197 | def progress(self,percent): 198 | self.writeSTDERR("%" + str(percent)) 199 | 200 | def debug(self,msg): 201 | self.writeSTDERR("D:" + str(msg)) 202 | 203 | 204 | 205 | def sanitise(value): 206 | replace_these = ["&",">","<"] 207 | replace_with = ["&",">","<"] 208 | tempvalue = value 209 | for i in range(0,len(replace_these)): 210 | tempvalue = tempvalue.replace(replace_these[i],replace_with[i]) 211 | return tempvalue -------------------------------------------------------------------------------- /Media/Block_length.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arieljt/VTCodeBlocks-Maltego/cf6db1586bc5aa00f3a13d6371fa9d6ce2b45e61/Media/Block_length.png -------------------------------------------------------------------------------- /Media/Codeblock_properties.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arieljt/VTCodeBlocks-Maltego/cf6db1586bc5aa00f3a13d6371fa9d6ce2b45e61/Media/Codeblock_properties.png -------------------------------------------------------------------------------- /Media/Example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arieljt/VTCodeBlocks-Maltego/cf6db1586bc5aa00f3a13d6371fa9d6ce2b45e61/Media/Example.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # VirusTotal Codeblocks Maltego Transforms 2 | 3 | ## Introduction 4 | These Maltego transforms allow you to pivot between different PE files based on codeblocks they share in common. 5 | One transform accepts a hash of a PE file and extracts its codeblocks over a set length threshold; the other transform accepts extracted codeblocks and return hashes of files containing them. This is achieved by using the unique codeblock ID returned from the `code-similar-to:` search modifier and running it with the `code-block:` search modifier in VirusTotal. 6 | 7 | For more information, please refer to the Webinar "*Visual investigations - Speed up your IR, Forensic Analysis and Hunting*" at https://www.brighttalk.com/webcast/18282/493986. 8 | 9 | ## Prerequisites 10 | - VirusTotal Private API key 11 | - Python 2.7.X, requests, json 12 | - Maltego 4.2.X 13 | 14 | ## Example 15 | ![Codeblocks](/Media/Example.png?raw=true) 16 | 17 | **Codeblock properties:** 18 | ![Codeblocks properties](/Media/Codeblock_properties.png?raw=true) 19 | 20 | ## Setup 21 | With the prerequisites met, clone repository to a local folder. 22 | 23 | 1. Edit both `HashToCodeblocks.py` and `CodeblocksToHash.py` and insert your VirusTotal private API key. 24 | 2. Import `VTCodeBlocks.mtz` to Maltego, making sure to import both the transforms and the entity. 25 | 3. Go to Transforms -> Transform Manager -> "[VT] Codeblock to Hash" and "[VT] Hash to Codeblock" and set: 26 | - Command line: `C:\Python27\python.exe` (or your python folder) 27 | - Working directory: The folder where you cloned this repository to. 28 | - Uncheck "Show debug info" 29 | 30 | ## Known issues 31 | Not an issue by itself, but you might get lots of short codeblocks, which might be undesired. You can easily edit the minimal codeblock length inside `HashToCodeblocks.py`: 32 | ![Minimal block length](/Media/Block_length.png?raw=true) 33 | 34 | -------------------------------------------------------------------------------- /VTCodeBlocks.mtz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arieljt/VTCodeBlocks-Maltego/cf6db1586bc5aa00f3a13d6371fa9d6ce2b45e61/VTCodeBlocks.mtz --------------------------------------------------------------------------------