From 782de168966b328a8f6806d0e1fe08ed970308a3 Mon Sep 17 00:00:00 2001 From: T0day Date: Fri, 25 Aug 2017 16:38:42 +0800 Subject: [PATCH 1/7] Upload the visualization part This is all what I have done so far for the visualization part. --- visualization/README.md | 32 ++ visualization/example_data.json | 312 +++++++++++++ visualization/generate_json.js | 551 +++++++++++++++++++++++ visualization/index.html | 111 +++++ visualization/relationship_d3.js | 738 +++++++++++++++++++++++++++++++ 5 files changed, 1744 insertions(+) create mode 100644 visualization/README.md create mode 100644 visualization/example_data.json create mode 100644 visualization/generate_json.js create mode 100644 visualization/index.html create mode 100644 visualization/relationship_d3.js diff --git a/visualization/README.md b/visualization/README.md new file mode 100644 index 0000000..36e13a2 --- /dev/null +++ b/visualization/README.md @@ -0,0 +1,32 @@ +# Visualization Part for Holmes Relationship + +## OverView +Visualization part is a website displaying the final relationships using d3.js. There are multiple views and tricks to improve the user experience. The page shows the relationships in a tree map. + +[See here](http://120.77.40.25/d3/index.html) + +### Purpose +Visualization can show the data more intuitively, helping the analysts do deep-dive investigations. + +## Multiple Views +###### These three views can be used together. +#### Threshold View +When we set a threshold score, those nodes whose scores are greater than the threshold are displayed. +#### Artefact View +When we click the artefact’s name, the nodes with this feature are added to ( or deleted from ) the map. +#### Quick View +This view shows a histogram that displays the number of nodes in different segments of scores. +## Tricks +#### Collapsible +When we click on the branch node, the branch node’s leaf nodes are folded and also the branch node is filled with steelblue. +#### Zoomable +We can change the map scale by mouse wheel and also translate the map by dragging. +#### Tooltip +When we move the mouse to the leaf node, the tooltip shows the final score, the reason that two samples are related, and the degree of this relation. +When we move the mouse to the branch node, the tooltip shows the branch name, the number of children in this branch, and the maximum score in this branch. +#### Visualization weight +In the whole map, the line weight is set according to the target node score. +In each set, the nodes are sorted by the score. +## Data +At present, the JSON data generated by [JSON GENERATOR](http://beta.json-generator.com), `generate_json.js` is the script to generate the data. +In the future, the JSON data will be generated from the final relationship table. diff --git a/visualization/example_data.json b/visualization/example_data.json new file mode 100644 index 0000000..d42f191 --- /dev/null +++ b/visualization/example_data.json @@ -0,0 +1,312 @@ +{ + "name": "599eb0ff208a37dc9db2f4f9", + "all_features": [ + "domain", + "pehash", + "imphash", + "ip" + ], + "children": [ + { + "name": "599eb0ff6a82f692f21c903a", + "final_score": "81.90", + "indicator_socre": "   Communicated with: 2 domains
   Communicated with: 2 ips
   Pehash similarity is: 66 
   Imphash similarity is: 69" + }, + { + "name": "599eb0ff7b26e1373bb1c1bd", + "final_score": "80.67", + "indicator_socre": "   Communicated with: 2 domains
   Communicated with: 3 ips
   Pehash similarity is: 65 
   Imphash similarity is: 69" + }, + { + "name": "599eb0ffa2225885663ee3bb", + "final_score": "83.93", + "indicator_socre": "   Communicated with: 2 domains
   Communicated with: 2 ips
   Pehash similarity is: 90 
   Imphash similarity is: 73" + }, + { + "name": [ + "domain", + "pehash", + "imphash" + ], + "final_score": "88.99", + "children": [ + { + "name": "599eb0ff2658debb81590605", + "final_score": "70.44", + "indicator_socre": "   Communicated with: 2 domains
   Pehash similarity is: 70 
   Imphash similarity is: 63" + }, + { + "name": "599eb0ff6b866d1f0aa5ce12", + "final_score": "68.99", + "indicator_socre": "   Communicated with: 2 domains
   Pehash similarity is: 69 
   Imphash similarity is: 57" + }, + { + "name": "599eb0ff6f2793e3154571fd", + "final_score": "88.99", + "indicator_socre": "   Communicated with: 2 domains
   Pehash similarity is: 79 
   Imphash similarity is: 92" + }, + { + "name": [ + "domain", + "pehash" + ], + "final_score": "63.04", + "children": [ + { + "name": "599eb0ff6f3979058e26c962", + "final_score": "54.08", + "indicator_socre": "   Communicated with: 1 domains
   Pehash similarity is: 72 
" + }, + { + "name": [ + "domain" + ], + "final_score": "63.04", + "children": [ + { + "name": "599eb0ff0ba8bc78a1455bae", + "final_score": "52.90", + "indicator_socre": "   Communicated with: 2 domains
" + }, + { + "name": "599eb0ff0d4665b3f1ca2ab6", + "final_score": "52.68", + "indicator_socre": "   Communicated with: 5 domains
" + }, + { + "name": "599eb0ff8df24593c3d04e0e", + "final_score": "63.04", + "indicator_socre": "   Communicated with: 1 domains
" + } + ] + }, + { + "name": [ + "pehash" + ], + "final_score": "58.67", + "children": [ + { + "name": "599eb100ca4c09733b697fad", + "final_score": "58.67", + "indicator_socre": "   Pehash similarity is: 59 
" + } + ] + } + ] + }, + { + "name": [ + "domain", + "imphash" + ], + "final_score": "83.42", + "children": [ + { + "name": "599eb1000cfcdddf4bd6bfbb", + "final_score": "83.42", + "indicator_socre": "   Communicated with: 8 domains
   Imphash similarity is: 73" + }, + { + "name": "599eb100c9546570455484df", + "final_score": "82.19", + "indicator_socre": "   Communicated with: 3 domains
   Imphash similarity is: 71" + }, + { + "name": [ + "imphash" + ], + "final_score": "69.33", + "children": [ + { + "name": "599eb100faceeefe1defd438", + "final_score": "69.33", + "indicator_socre": "   Imphash similarity is: 62" + }, + { + "name": "599eb10095c104464a360010", + "final_score": "52.27", + "indicator_socre": "   Imphash similarity is: 50" + }, + { + "name": "599eb100b3437ac53e6aac87", + "final_score": "54.61", + "indicator_socre": "   Imphash similarity is: 51" + } + ] + } + ] + }, + { + "name": [ + "pehash", + "imphash" + ], + "final_score": "85.13", + "children": [ + { + "name": "599eb1008913d211391e6978", + "final_score": "85.13", + "indicator_socre": "   Pehash similarity is: 68 
   Imphash similarity is: 78" + }, + { + "name": "599eb1003060e42d64728677", + "final_score": "58.96", + "indicator_socre": "   Pehash similarity is: 67 
   Imphash similarity is: 63" + }, + { + "name": "599eb100cbe0305187abcc2f", + "final_score": "74.86", + "indicator_socre": "   Pehash similarity is: 100 
   Imphash similarity is: 68" + }, + { + "name": "599eb100b7c0fe20d6f4cf2c", + "final_score": "52.93", + "indicator_socre": "   Pehash similarity is: 47 
   Imphash similarity is: 51" + } + ] + } + ] + }, + { + "name": [ + "domain", + "pehash", + "ip" + ], + "final_score": "94.62", + "children": [ + { + "name": "599eb100fddc455b6ca699e4", + "final_score": "93.15", + "indicator_socre": "   Communicated with: 5 domains
   Communicated with: 2 ips
   Pehash similarity is: 124 
" + }, + { + "name": "599eb100597bf5aa48a37fda", + "final_score": "82.79", + "indicator_socre": "   Communicated with: 2 domains
   Communicated with: 2 ips
   Pehash similarity is: 83 
" + }, + { + "name": [ + "domain", + "ip" + ], + "final_score": "94.62", + "children": [ + { + "name": "599eb1006cb4447e9a2efe47", + "final_score": "94.62", + "indicator_socre": "   Communicated with: 2 domains
   Communicated with: 3 ips
" + }, + { + "name": "599eb100f9c7caa7689d9e5a", + "final_score": "71.10", + "indicator_socre": "   Communicated with: 1 domains
   Communicated with: 1 ips
" + }, + { + "name": "599eb1007623f349a4402e73", + "final_score": "53.27", + "indicator_socre": "   Communicated with: 3 domains
   Communicated with: 1 ips
" + }, + { + "name": [ + "ip" + ], + "final_score": "60.30", + "children": [ + { + "name": "599eb100329fb907154227d7", + "final_score": "60.30", + "indicator_socre": "   Communicated with: 6 ips
" + } + ] + } + ] + }, + { + "name": [ + "pehash", + "ip" + ], + "final_score": "61.47", + "children": [ + { + "name": "599eb100f3c2bd8b0a3db7ba", + "final_score": "61.47", + "indicator_socre": "   Communicated with: 1 ips
   Pehash similarity is: 82 
" + } + ] + } + ] + }, + { + "name": [ + "domain", + "imphash", + "ip" + ], + "final_score": "79.29", + "children": [ + { + "name": "599eb100837a5ad13bba4122", + "final_score": "65.58", + "indicator_socre": "   Communicated with: 2 domains
   Communicated with: 2 ips
   Imphash similarity is: 78" + }, + { + "name": "599eb1003f0ba0a3bb9a8087", + "final_score": "75.63", + "indicator_socre": "   Communicated with: 4 domains
   Communicated with: 3 ips
   Imphash similarity is: 64" + }, + { + "name": "599eb100f02a4391e85d3f2b", + "final_score": "79.29", + "indicator_socre": "   Communicated with: 8 domains
   Communicated with: 2 ips
   Imphash similarity is: 85" + }, + { + "name": [ + "imphash", + "ip" + ], + "final_score": "66.53", + "children": [ + { + "name": "599eb1004b01b5ffda48a0ab", + "final_score": "66.53", + "indicator_socre": "   Communicated with: 2 ips
   Imphash similarity is: 53" + }, + { + "name": "599eb1002ca2a2e6edde9d6e", + "final_score": "60.22", + "indicator_socre": "   Communicated with: 2 ips
   Imphash similarity is: 63" + } + ] + } + ] + }, + { + "name": [ + "domain", + "imphash", + "ip" + ], + "final_score": "78.87", + "children": [ + { + "name": "599eb100287e38857c6d8887", + "final_score": "66.66", + "indicator_socre": "   Communicated with: 3 domains
   Communicated with: 3 ips
   Imphash similarity is: 85" + }, + { + "name": "599eb1002195d39ffbb35b38", + "final_score": "69.58", + "indicator_socre": "   Communicated with: 7 domains
   Communicated with: 2 ips
   Imphash similarity is: 65" + }, + { + "name": "599eb100b1c0209afa98e3c1", + "final_score": "78.87", + "indicator_socre": "   Communicated with: 2 domains
   Communicated with: 3 ips
   Imphash similarity is: 85" + } + ] + } + ] +} diff --git a/visualization/generate_json.js b/visualization/generate_json.js new file mode 100644 index 0000000..7369f76 --- /dev/null +++ b/visualization/generate_json.js @@ -0,0 +1,551 @@ +{ + name: '{{objectId()}}', + all_features: ["domain","pehash","imphash","ip"], + children:[ + { + 'repeat(3,3)':{ + name: '{{objectId()}}', + final_score: '{{floating(80, 90, 2,"0.00")}}', + indicator_socre: function (tags,parent){ + var showstring = ""; + showstring = showstring + "   Communicated with: " + Math.round(this.final_score / tags.integer(1, 5) / 10) + " domains
" + "   Communicated with: " + Math.round(this.final_score / tags.integer(1, 5) / 10)+ " ips
" + "   Pehash similarity is: " + Math.round(this.final_score / tags.integer(6,10) * 8) + " 
" + "   Imphash similarity is: " + Math.round(this.final_score / tags.integer(60,100) * 80); + return showstring; + } + } + }, + { + name: ["domain","pehash","imphash"], + final_score: '{{floating(80, 100, 2,"0.00")}}', + children:[ + { + 'repeat(2,2)':{ + name: '{{objectId()}}', + final_score: function (tags,parent){ + return (tags.floating(50, parent.final_score, 2,"0.00")); + }, + indicator_socre: function (tags,parent){ + var showstring = ""; + if (parent.name.indexOf("domain") >= 0) + showstring = showstring + "   Communicated with: " + Math.round(this.final_score / tags.integer(1, 5) / 10) + " domains
"; + if (parent.name.indexOf("ip") >= 0) + showstring = showstring + "   Communicated with: " + Math.round(this.final_score / tags.integer(1, 5) / 10)+ " ips
"; + if (parent.name.indexOf("pehash") >= 0) + showstring = showstring + "   Pehash similarity is: " + Math.round(this.final_score / tags.integer(6,10) * 8) + " 
"; + if (parent.name.indexOf("imphash") >= 0) + showstring = showstring + "   Imphash similarity is: " + Math.round(this.final_score / tags.integer(60,100) * 80); + return showstring; + } + } + }, + { + name: '{{objectId()}}', + final_score: function (tags,parent){ + return parent.final_score; + }, + indicator_socre: function (tags,parent){ + var showstring = ""; + if (parent.name.indexOf("domain") >= 0) + showstring = showstring + "   Communicated with: " + Math.round(this.final_score / tags.integer(1, 5) / 10) + " domains
"; + if (parent.name.indexOf("ip") >= 0) + showstring = showstring + "   Communicated with: " + Math.round(this.final_score / tags.integer(1, 5) / 10)+ " ips
"; + if (parent.name.indexOf("pehash") >= 0) + showstring = showstring + "   Pehash similarity is: " + Math.round(this.final_score / tags.integer(6,10) * 8) + " 
"; + if (parent.name.indexOf("imphash") >= 0) + showstring = showstring + "   Imphash similarity is: " + Math.round(this.final_score / tags.integer(60,100) * 80); + return showstring; + } + }, + { + name: ["domain","pehash"], + final_score: function (tags,parent){ + return (tags.floating(60, parent.final_score, 2,"0.00")); + }, + children:[ + { + name: '{{objectId()}}', + final_score: function (tags,parent){ + return (tags.floating(50, parent.final_score, 2,"0.00")); + }, + indicator_socre: function (tags,parent){ + var showstring = ""; + if (parent.name.indexOf("domain") >= 0) + showstring = showstring + "   Communicated with: " + Math.round(this.final_score / tags.integer(1, 5) / 10) + " domains
"; + if (parent.name.indexOf("ip") >= 0) + showstring = showstring + "   Communicated with: " + Math.round(this.final_score / tags.integer(1, 5) / 10)+ " ips
"; + if (parent.name.indexOf("pehash") >= 0) + showstring = showstring + "   Pehash similarity is: " + Math.round(this.final_score / tags.integer(6,10) * 8) + " 
"; + if (parent.name.indexOf("imphash") >= 0) + showstring = showstring + "   Imphash similarity is: " + Math.round(this.final_score / tags.integer(60,100) * 80); + return showstring; + } + }, + { + name: ["domain"], + final_score: function (tags,parent){ + return parent.final_score; + }, + children:[ + { + 'repeat(2,2)':{ + name: '{{objectId()}}', + final_score: function (tags,parent){ + return (tags.floating(30, parent.final_score, 2,"0.00")); + }, + indicator_socre: function (tags,parent){ + var showstring = ""; + if (parent.name.indexOf("domain") >= 0) + showstring = showstring + "   Communicated with: " + Math.round(this.final_score / tags.integer(1, 5) / 10) + " domains
"; + if (parent.name.indexOf("ip") >= 0) + showstring = showstring + "   Communicated with: " + Math.round(this.final_score / tags.integer(1, 5) / 10)+ " ips
"; + if (parent.name.indexOf("pehash") >= 0) + showstring = showstring + "   Pehash similarity is: " + Math.round(this.final_score / tags.integer(6,10) * 8) + " 
"; + if (parent.name.indexOf("imphash") >= 0) + showstring = showstring + "   Imphash similarity is: " + Math.round(this.final_score / tags.integer(60,100) * 80); + return showstring; + } + } + }, + { + name: '{{objectId()}}', + final_score: function (tags,parent){ + return parent.final_score; + }, + indicator_socre: function (tags,parent){ + var showstring = ""; + if (parent.name.indexOf("domain") >= 0) + showstring = showstring + "   Communicated with: " + Math.round(this.final_score / tags.integer(1, 5) / 10) + " domains
"; + if (parent.name.indexOf("ip") >= 0) + showstring = showstring + "   Communicated with: " + Math.round(this.final_score / tags.integer(1, 5) / 10)+ " ips
"; + if (parent.name.indexOf("pehash") >= 0) + showstring = showstring + "   Pehash similarity is: " + Math.round(this.final_score / tags.integer(6,10) * 8) + " 
"; + if (parent.name.indexOf("imphash") >= 0) + showstring = showstring + "   Imphash similarity is: " + Math.round(this.final_score / tags.integer(60,100) * 80); + return showstring; + } + } + ] + }, + { + name: ["pehash"], + final_score: function (tags,parent){ + return (tags.floating(40, parent.final_score, 2,"0.00")); + }, + children:[ + { + name: '{{objectId()}}', + final_score: function (tags,parent){ + return parent.final_score; + }, + indicator_socre: function (tags,parent){ + var showstring = ""; + if (parent.name.indexOf("domain") >= 0) + showstring = showstring + "   Communicated with: " + Math.round(this.final_score / tags.integer(1, 5) / 10) + " domains
"; + if (parent.name.indexOf("ip") >= 0) + showstring = showstring + "   Communicated with: " + Math.round(this.final_score / tags.integer(1, 5) / 10)+ " ips
"; + if (parent.name.indexOf("pehash") >= 0) + showstring = showstring + "   Pehash similarity is: " + Math.round(this.final_score / tags.integer(6,10) * 8) + " 
"; + if (parent.name.indexOf("imphash") >= 0) + showstring = showstring + "   Imphash similarity is: " + Math.round(this.final_score / tags.integer(60,100) * 80); + return showstring; + } + } + ] + } + ] + }, + { + name: ["domain","imphash"], + final_score: function (tags,parent){ + return (tags.floating(60, parent.final_score, 2,"0.00")); + }, + children:[ + { + name: '{{objectId()}}', + final_score: function (tags,parent){ + return parent.final_score; + }, + indicator_socre: function (tags,parent){ + var showstring = ""; + if (parent.name.indexOf("domain") >= 0) + showstring = showstring + "   Communicated with: " + Math.round(this.final_score / tags.integer(1, 5) / 10) + " domains
"; + if (parent.name.indexOf("ip") >= 0) + showstring = showstring + "   Communicated with: " + Math.round(this.final_score / tags.integer(1, 5) / 10)+ " ips
"; + if (parent.name.indexOf("pehash") >= 0) + showstring = showstring + "   Pehash similarity is: " + Math.round(this.final_score / tags.integer(6,10) * 8) + " 
"; + if (parent.name.indexOf("imphash") >= 0) + showstring = showstring + "   Imphash similarity is: " + Math.round(this.final_score / tags.integer(60,100) * 80); + return showstring; + } + }, + { + name: '{{objectId()}}', + final_score: function (tags,parent){ + return (tags.floating(40, parent.final_score, 2,"0.00")); + }, + indicator_socre: function (tags,parent){ + var showstring = ""; + if (parent.name.indexOf("domain") >= 0) + showstring = showstring + "   Communicated with: " + Math.round(this.final_score / tags.integer(1, 5) / 10) + " domains
"; + if (parent.name.indexOf("ip") >= 0) + showstring = showstring + "   Communicated with: " + Math.round(this.final_score / tags.integer(1, 5) / 10)+ " ips
"; + if (parent.name.indexOf("pehash") >= 0) + showstring = showstring + "   Pehash similarity is: " + Math.round(this.final_score / tags.integer(6,10) * 8) + " 
"; + if (parent.name.indexOf("imphash") >= 0) + showstring = showstring + "   Imphash similarity is: " + Math.round(this.final_score / tags.integer(60,100) * 80); + return showstring; + } + }, + { + name: ["imphash"], + final_score: function (tags,parent){ + return (tags.floating(40, parent.final_score, 2,"0.00")); + }, + children:[ + { + name: '{{objectId()}}', + final_score: function (tags,parent){ + return parent.final_score; + }, + indicator_socre: function (tags,parent){ + var showstring = ""; + if (parent.name.indexOf("domain") >= 0) + showstring = showstring + "   Communicated with: " + Math.round(this.final_score / tags.integer(1, 5) / 10) + " domains
"; + if (parent.name.indexOf("ip") >= 0) + showstring = showstring + "   Communicated with: " + Math.round(this.final_score / tags.integer(1, 5) / 10)+ " ips
"; + if (parent.name.indexOf("pehash") >= 0) + showstring = showstring + "   Pehash similarity is: " + Math.round(this.final_score / tags.integer(6,10) * 8) + " 
"; + if (parent.name.indexOf("imphash") >= 0) + showstring = showstring + "   Imphash similarity is: " + Math.round(this.final_score / tags.integer(60,100) * 80); + return showstring; + } + }, + { + 'repeat(2,2)':{ + name: '{{objectId()}}', + final_score: function (tags,parent){ + return (tags.floating(30, parent.final_score, 2,"0.00")); + }, + indicator_socre: function (tags,parent){ + var showstring = ""; + if (parent.name.indexOf("domain") >= 0) + showstring = showstring + "   Communicated with: " + Math.round(this.final_score / tags.integer(1, 5) / 10) + " domains
"; + if (parent.name.indexOf("ip") >= 0) + showstring = showstring + "   Communicated with: " + Math.round(this.final_score / tags.integer(1, 5) / 10)+ " ips
"; + if (parent.name.indexOf("pehash") >= 0) + showstring = showstring + "   Pehash similarity is: " + Math.round(this.final_score / tags.integer(6,10) * 8) + " 
"; + if (parent.name.indexOf("imphash") >= 0) + showstring = showstring + "   Imphash similarity is: " + Math.round(this.final_score / tags.integer(60,100) * 80); + return showstring; + } + } + } + ] + } + ] + }, + { + name: ["pehash","imphash"], + final_score: function (tags,parent){ + return (tags.floating(50, parent.final_score, 2,"0.00")); + }, + children:[ + { + name: '{{objectId()}}', + final_score: function (tags,parent){ + return parent.final_score; + }, + indicator_socre: function (tags,parent){ + var showstring = ""; + if (parent.name.indexOf("domain") >= 0) + showstring = showstring + "   Communicated with: " + Math.round(this.final_score / tags.integer(1, 5) / 10) + " domains
"; + if (parent.name.indexOf("ip") >= 0) + showstring = showstring + "   Communicated with: " + Math.round(this.final_score / tags.integer(1, 5) / 10)+ " ips
"; + if (parent.name.indexOf("pehash") >= 0) + showstring = showstring + "   Pehash similarity is: " + Math.round(this.final_score / tags.integer(6,10) * 8) + " 
"; + if (parent.name.indexOf("imphash") >= 0) + showstring = showstring + "   Imphash similarity is: " + Math.round(this.final_score / tags.integer(60,100) * 80); + return showstring; + } + }, + { + 'repeat(3,3)':{ + name: '{{objectId()}}', + final_score: function (tags,parent){ + return (tags.floating(50, parent.final_score, 2,"0.00")); + }, + indicator_socre: function (tags,parent){ + var showstring = ""; + if (parent.name.indexOf("domain") >= 0) + showstring = showstring + "   Communicated with: " + Math.round(this.final_score / tags.integer(1, 5) / 10) + " domains
"; + if (parent.name.indexOf("ip") >= 0) + showstring = showstring + "   Communicated with: " + Math.round(this.final_score / tags.integer(1, 5) / 10)+ " ips
"; + if (parent.name.indexOf("pehash") >= 0) + showstring = showstring + "   Pehash similarity is: " + Math.round(this.final_score / tags.integer(6,10) * 8) + " 
"; + if (parent.name.indexOf("imphash") >= 0) + showstring = showstring + "   Imphash similarity is: " + Math.round(this.final_score / tags.integer(60,100) * 80); + return showstring; + } + } + } + ] + } + ] + }, + { + name: ["domain","pehash","ip"], + final_score: '{{floating(70, 100, 2,"0.00")}}', + children:[ + { + 'repeat(2,2)':{ + name: '{{objectId()}}', + final_score: function (tags,parent){ + return (tags.floating(50, parent.final_score, 2,"0.00")); + }, + indicator_socre: function (tags,parent){ + var showstring = ""; + if (parent.name.indexOf("domain") >= 0) + showstring = showstring + "   Communicated with: " + Math.round(this.final_score / tags.integer(1, 5) / 10) + " domains
"; + if (parent.name.indexOf("ip") >= 0) + showstring = showstring + "   Communicated with: " + Math.round(this.final_score / tags.integer(1, 5) / 10)+ " ips
"; + if (parent.name.indexOf("pehash") >= 0) + showstring = showstring + "   Pehash similarity is: " + Math.round(this.final_score / tags.integer(6,10) * 8) + " 
"; + if (parent.name.indexOf("imphash") >= 0) + showstring = showstring + "   Imphash similarity is: " + Math.round(this.final_score / tags.integer(60,100) * 80); + return showstring; + } + } + }, + { + name: ["domain","ip"], + final_score: function (tags,parent){ + return parent.final_score; + }, + children:[ + { + name: '{{objectId()}}', + final_score: function (tags,parent){ + return parent.final_score; + }, + indicator_socre: function (tags,parent){ + var showstring = ""; + if (parent.name.indexOf("domain") >= 0) + showstring = showstring + "   Communicated with: " + Math.round(this.final_score / tags.integer(1, 5) / 10) + " domains
"; + if (parent.name.indexOf("ip") >= 0) + showstring = showstring + "   Communicated with: " + Math.round(this.final_score / tags.integer(1, 5) / 10)+ " ips
"; + if (parent.name.indexOf("pehash") >= 0) + showstring = showstring + "   Pehash similarity is: " + Math.round(this.final_score / tags.integer(6,10) * 8) + " 
"; + if (parent.name.indexOf("imphash") >= 0) + showstring = showstring + "   Imphash similarity is: " + Math.round(this.final_score / tags.integer(60,100) * 80); + return showstring; + } + }, + { + 'repeat(2,2)':{ + name: '{{objectId()}}', + final_score: function (tags,parent){ + return (tags.floating(40, parent.final_score, 2,"0.00")); + }, + indicator_socre: function (tags,parent){ + var showstring = ""; + if (parent.name.indexOf("domain") >= 0) + showstring = showstring + "   Communicated with: " + Math.round(this.final_score / tags.integer(1, 5) / 10) + " domains
"; + if (parent.name.indexOf("ip") >= 0) + showstring = showstring + "   Communicated with: " + Math.round(this.final_score / tags.integer(1, 5) / 10)+ " ips
"; + if (parent.name.indexOf("pehash") >= 0) + showstring = showstring + "   Pehash similarity is: " + Math.round(this.final_score / tags.integer(6,10) * 8) + " 
"; + if (parent.name.indexOf("imphash") >= 0) + showstring = showstring + "   Imphash similarity is: " + Math.round(this.final_score / tags.integer(60,100) * 80); + return showstring; + } + } + }, + { + name: ["ip"], + final_score: function (tags,parent){ + return (tags.floating(40, parent.final_score, 2,"0.00")); + }, + children:[ + { + name: '{{objectId()}}', + final_score: function (tags,parent){ + return parent.final_score; + }, + indicator_socre: function (tags,parent){ + var showstring = ""; + if (parent.name.indexOf("domain") >= 0) + showstring = showstring + "   Communicated with: " + Math.round(this.final_score / tags.integer(1, 5) / 10) + " domains
"; + if (parent.name.indexOf("ip") >= 0) + showstring = showstring + "   Communicated with: " + Math.round(this.final_score / tags.integer(1, 5) / 10)+ " ips
"; + if (parent.name.indexOf("pehash") >= 0) + showstring = showstring + "   Pehash similarity is: " + Math.round(this.final_score / tags.integer(6,10) * 8) + " 
"; + if (parent.name.indexOf("imphash") >= 0) + showstring = showstring + "   Imphash similarity is: " + Math.round(this.final_score / tags.integer(60,100) * 80); + return showstring; + } + } + ] + } + ] + }, + { + name: ["pehash","ip"], + final_score: function (tags,parent){ + return (tags.floating(60, parent.final_score, 2,"0.00")); + }, + children:[ + { + name: '{{objectId()}}', + final_score: function (tags,parent){ + return parent.final_score; + }, + indicator_socre: function (tags,parent){ + var showstring = ""; + if (parent.name.indexOf("domain") >= 0) + showstring = showstring + "   Communicated with: " + Math.round(this.final_score / tags.integer(1, 5) / 10) + " domains
"; + if (parent.name.indexOf("ip") >= 0) + showstring = showstring + "   Communicated with: " + Math.round(this.final_score / tags.integer(1, 5) / 10)+ " ips
"; + if (parent.name.indexOf("pehash") >= 0) + showstring = showstring + "   Pehash similarity is: " + Math.round(this.final_score / tags.integer(6,10) * 8) + " 
"; + if (parent.name.indexOf("imphash") >= 0) + showstring = showstring + "   Imphash similarity is: " + Math.round(this.final_score / tags.integer(60,100) * 80); + return showstring; + } + } + ] + } + ] + }, + { + name: ["domain","imphash","ip"], + final_score: '{{floating(70, 80, 2,"0.00")}}', + children:[ + { + 'repeat(2,2)':{ + name: '{{objectId()}}', + final_score: function (tags,parent){ + return (tags.floating(60, parent.final_score, 2,"0.00")); + }, + indicator_socre: function (tags,parent){ + var showstring = ""; + if (parent.name.indexOf("domain") >= 0) + showstring = showstring + "   Communicated with: " + Math.round(this.final_score / tags.integer(1, 5) / 10) + " domains
"; + if (parent.name.indexOf("ip") >= 0) + showstring = showstring + "   Communicated with: " + Math.round(this.final_score / tags.integer(1, 5) / 10)+ " ips
"; + if (parent.name.indexOf("pehash") >= 0) + showstring = showstring + "   Pehash similarity is: " + Math.round(this.final_score / tags.integer(6,10) * 8) + " 
"; + if (parent.name.indexOf("imphash") >= 0) + showstring = showstring + "   Imphash similarity is: " + Math.round(this.final_score / tags.integer(60,100) * 80); + return showstring; + } + } + }, + { + name: '{{objectId()}}', + final_score: function (tags,parent){ + return parent.final_score; + }, + indicator_socre: function (tags,parent){ + var showstring = ""; + if (parent.name.indexOf("domain") >= 0) + showstring = showstring + "   Communicated with: " + Math.round(this.final_score / tags.integer(1, 5) / 10) + " domains
"; + if (parent.name.indexOf("ip") >= 0) + showstring = showstring + "   Communicated with: " + Math.round(this.final_score / tags.integer(1, 5) / 10)+ " ips
"; + if (parent.name.indexOf("pehash") >= 0) + showstring = showstring + "   Pehash similarity is: " + Math.round(this.final_score / tags.integer(6,10) * 8) + " 
"; + if (parent.name.indexOf("imphash") >= 0) + showstring = showstring + "   Imphash similarity is: " + Math.round(this.final_score / tags.integer(60,100) * 80); + return showstring; + } + }, + { + name: ["imphash","ip"], + final_score: function (tags,parent){ + return (tags.floating(60, parent.final_score, 2,"0.00")); + }, + children:[ + { + name: '{{objectId()}}', + final_score: function (tags,parent){ + return parent.final_score; + }, + indicator_socre: function (tags,parent){ + var showstring = ""; + if (parent.name.indexOf("domain") >= 0) + showstring = showstring + "   Communicated with: " + Math.round(this.final_score / tags.integer(1, 5) / 10) + " domains
"; + if (parent.name.indexOf("ip") >= 0) + showstring = showstring + "   Communicated with: " + Math.round(this.final_score / tags.integer(1, 5) / 10)+ " ips
"; + if (parent.name.indexOf("pehash") >= 0) + showstring = showstring + "   Pehash similarity is: " + Math.round(this.final_score / tags.integer(6,10) * 8) + " 
"; + if (parent.name.indexOf("imphash") >= 0) + showstring = showstring + "   Imphash similarity is: " + Math.round(this.final_score / tags.integer(60,100) * 80); + return showstring; + } + }, + { + name: '{{objectId()}}', + final_score: function (tags,parent){ + return (tags.floating(60, parent.final_score, 2,"0.00")); + }, + indicator_socre: function (tags,parent){ + var showstring = ""; + if (parent.name.indexOf("domain") >= 0) + showstring = showstring + "   Communicated with: " + Math.round(this.final_score / tags.integer(1, 5) / 10) + " domains
"; + if (parent.name.indexOf("ip") >= 0) + showstring = showstring + "   Communicated with: " + Math.round(this.final_score / tags.integer(1, 5) / 10)+ " ips
"; + if (parent.name.indexOf("pehash") >= 0) + showstring = showstring + "   Pehash similarity is: " + Math.round(this.final_score / tags.integer(6,10) * 8) + " 
"; + if (parent.name.indexOf("imphash") >= 0) + showstring = showstring + "   Imphash similarity is: " + Math.round(this.final_score / tags.integer(60,100) * 80); + return showstring; + } + } + ] + } + ] + }, + { + name: ["domain","imphash","ip"], + final_score: '{{floating(70, 80, 2,"0.00")}}', + children:[ + { + 'repeat(2,2)':{ + name: '{{objectId()}}', + final_score: function (tags,parent){ + return (tags.floating(60, parent.final_score, 2,"0.00")); + }, + indicator_socre: function (tags,parent){ + var showstring = ""; + if (parent.name.indexOf("domain") >= 0) + showstring = showstring + "   Communicated with: " + Math.round(this.final_score / tags.integer(1, 5) / 10) + " domains
"; + if (parent.name.indexOf("ip") >= 0) + showstring = showstring + "   Communicated with: " + Math.round(this.final_score / tags.integer(1, 5) / 10)+ " ips
"; + if (parent.name.indexOf("pehash") >= 0) + showstring = showstring + "   Pehash similarity is: " + Math.round(this.final_score / tags.integer(6,10) * 8) + " 
"; + if (parent.name.indexOf("imphash") >= 0) + showstring = showstring + "   Imphash similarity is: " + Math.round(this.final_score / tags.integer(60,100) * 80); + return showstring; + } + } + }, + { + name: '{{objectId()}}', + final_score: function (tags,parent){ + return parent.final_score; + }, + indicator_socre: function (tags,parent){ + var showstring = ""; + if (parent.name.indexOf("domain") >= 0) + showstring = showstring + "   Communicated with: " + Math.round(this.final_score / tags.integer(1, 5) / 10) + " domains
"; + if (parent.name.indexOf("ip") >= 0) + showstring = showstring + "   Communicated with: " + Math.round(this.final_score / tags.integer(1, 5) / 10)+ " ips
"; + if (parent.name.indexOf("pehash") >= 0) + showstring = showstring + "   Pehash similarity is: " + Math.round(this.final_score / tags.integer(6,10) * 8) + " 
"; + if (parent.name.indexOf("imphash") >= 0) + showstring = showstring + "   Imphash similarity is: " + Math.round(this.final_score / tags.integer(60,100) * 80); + return showstring; + } + } + ] + } + ] +} diff --git a/visualization/index.html b/visualization/index.html new file mode 100644 index 0000000..a089d93 --- /dev/null +++ b/visualization/index.html @@ -0,0 +1,111 @@ + + +Holmes_relationship + + + + + +
+ + diff --git a/visualization/relationship_d3.js b/visualization/relationship_d3.js new file mode 100644 index 0000000..88ae183 --- /dev/null +++ b/visualization/relationship_d3.js @@ -0,0 +1,738 @@ +treeJSON = d3.json("example_data.json", function(error, treeData) { + var totalNodes = 0; + var maxLabelLength = 0; + var i = 0; + var duration = 750; + var root + var len = 0 + var newroot = {} + var viewerWidth = $(document).width(); + var viewerHeight = $(document).height(); + var allFeatures = treeData.all_features + var allFeaturesInLoop = treeData.all_features + var deleteFeatues = [] + var chartflag = 0 + + var tree = d3.layout.tree() + .size([viewerHeight, viewerWidth]); + var diagonal = d3.svg.diagonal() + .projection(function(d) { + return [d.y, d.x]; + }); + + function visit(parent, visitFn, childrenFn) { + if (!parent) return; + visitFn(parent); + var children = childrenFn(parent); + if (children) { + var count = children.length; + for (var i = 0; i < count; i++) { + visit(children[i], visitFn, childrenFn); + } + } + } + + visit(treeData, function(d) { + totalNodes++; + maxLabelLength = Math.max(d.name.length, maxLabelLength); + + }, function(d) { + return d.children && d.children.length > 0 ? d.children : null; + }); + + function sortTree() { + tree.sort(function(a, b) { + if (a.final_score == b.final_score){ + return b.name.toString().toLowerCase() < a.name.toString().toLowerCase() ? 1 : -1; + } + return b.final_score > a.final_score ? 1 : -1; + }); + } + sortTree(); + + function zoom() { + svgGroup.attr("transform", "translate(" + d3.event.translate + ")scale(" + d3.event.scale + ")"); + } + + var zoomListener = d3.behavior.zoom().scaleExtent([0, 3]).on("zoom", zoom); + + var baseSvg = d3.select("#relationship-container").append("svg") + .attr("width", viewerWidth) + .attr("height", viewerHeight) + .attr("class", "overlay") + .call(zoomListener); + + function centerNode(source) { + scale = zoomListener.scale(); + x = -source.y0; + y = -source.x0; + x = x * scale + viewerWidth / 2; + y = y * scale + viewerHeight / 2; + d3.select('g').transition() + .duration(duration) + .attr("transform", "translate(" + (x-viewerWidth/4) + "," + (y-20) + ")scale(" + scale + ")"); + zoomListener.scale(scale); + zoomListener.translate([x, y]); + } + + function toggleChildren(d) { + if (d.children) { + d._children = d.children; + d.children = null; + } else if (d._children) { + d.children = d._children; + d._children = null; + } + return d; + } + + function click(d) { + if (d3.event.defaultPrevented) return; + d = toggleChildren(d); + update(d); + centerNode(d); + } + + function thresholdInitial(batch_node){ + if (batch_node.children_threshold || batch_node._children_threshold) { + if (batch_node.children_threshold){ + if (!batch_node.children) {batch_node.children=[]} + for (var i = 0; i < batch_node.children.length; i++){ + thresholdInitial(batch_node.children[i]) + } + for (var i = 0; i < batch_node.children_threshold.length ; i++){ + batch_node.children.push(batch_node.children_threshold[i]) + thresholdInitial(batch_node.children_threshold[i]) + } + batch_node.children_threshold=null + } + if (batch_node._children_threshold){ + if (!batch_node._children) {batch_node._children=[]} + for (var i = 0; i < batch_node._children.length; i++){ + thresholdInitial(batch_node._children[i]) + } + for (var j = 0; j < batch_node._children_threshold.length ; j++){ + batch_node._children.push(batch_node._children_threshold[j]) + thresholdInitial(batch_node._children_threshold[j]) + } + batch_node._children_threshold=null + } + } + else if (batch_node.children || batch_node._children){ + if (batch_node.children){ + for (var i = 0; i < batch_node.children.length ; i++){ + thresholdInitial(batch_node.children[i]) + } + } + if (batch_node._children){ + for (var i = 0; i < batch_node._children.length ; i++){ + thresholdInitial(batch_node._children[i]) + } + } + } + } + + function thresholdChildren(batch_node,number) { + if (batch_node.children || batch_node._children) { + if (batch_node.children){ + batch_node.children_threshold = [] + for (var i = batch_node.children.length-1; i >=0 ; i--){ + if (batch_node.children[i].final_score >= number) { + if (batch_node.children[i].children) { + thresholdChildren(batch_node.children[i],number) + } + } + else { + batch_node.children_threshold.splice(0,0, batch_node.children[i]) + batch_node.children.splice(i,1) + } + } + } + if (batch_node._children){ + batch_node._children_threshold = [] + for (var j = batch_node._children.length-1; j >=0 ; j--){ + if (batch_node._children[j].final_score >= number) { + if (batch_node._children[j].children) { + thresholdChildren(batch_node._children[j],number) + } + } + else { + batch_node._children_threshold.splice(0,0, batch_node._children[j]) + batch_node._children.splice(j,1) + } + } + } + } + } + + Array.prototype.indexOf = function(val) { + for (var i = 0; i < this.length; i++) { + if (this[i] == val) return i; + } + return -1; + }; + + Array.prototype.findAll = function(val) { + var flag = [] + for (var i = 0; i < this.length; i++) { + if (this[i].name.toString() == val.name.toString()) { + flag.push(i) + } + } + return flag; + }; + + Array.prototype.remove = function(val) { + var index = this.indexOf(val); + if (index > -1) { + this.splice(index, 1); + } + }; + + function deleteArtefact(name) { + allFeaturesInLoop.remove(name) + allFeaturesInLoop = allFeaturesInLoop.sort() + var nodes = tree.nodes(root).reverse() + nodes.forEach(function(d) { + if (typeof(d.name)=="object"){ + if ($.inArray(name, d.name) >= 0) { + if (d.name.length == 1){ + d.children = null + d.parent.children.remove(d) + } + else{ + d.name.remove(name) + } + } + } + }); + update(root) + nodes.forEach(function(d) { + if (typeof(d.name)=="object"){ + if ( (d.name.toString() == d.parent.name.toString()) || (d.name.sort().toString() == allFeaturesInLoop.sort().toString()) ){ + for (var i = 0; i < d.children.length; i++) { + d.parent.children.splice(-1,0,d.children[i]); + } + d.children = null + d.parent.children.remove(d) + } + } + }); + update(root) + nodes.forEach(function(d) { + if (typeof(d.name)=="object" && d.children != null && d.parent.children != null){ + var sameNameLabel = d.parent.children.findAll(d) + if (sameNameLabel.length > 1){ + var sameMaxScore = d.parent.children[sameNameLabel[0]].final_score + for (var i = sameNameLabel.length-1 ; i >=1 ; i--){ + + if (d.parent.children[sameNameLabel[i]].final_score > sameMaxScore) { + sameMaxScore = d.parent.children[sameNameLabel[i]].final_score + } + for (var j =0; j < d.parent.children[sameNameLabel[i]].children.length; j++){ + d.parent.children[sameNameLabel[0]].children.splice(-1,0,d.parent.children[sameNameLabel[i]].children[j]) + } + d.parent.children[sameNameLabel[i]].children = null + d.parent.children.remove(d.parent.children[sameNameLabel[i]]) + } + d.final_score = sameMaxScore + } + } + }); + update(root) + } + + function deleteArtefactLoop(name){ + root=clone(newroot); + allFeaturesInLoop = treeData.all_features + for (var i = 0; i < name.length; i++) { + deleteArtefact(name[i]) + } + update(root) + } + + function createChart(){ + + var chartSvg = baseSvg.append("svg") + .attr("class","chart") + .attr("x",250) + .attr("y",100) + .attr("width", viewerWidth) + .attr("height", viewerHeight) + .attr("transform","translate(" + 0 + "," + 0 + ")") + + var scoreArray = [0] + var nodes = tree.nodes(root).reverse() + nodes.forEach(function(d) { + if (!(d.children || d._children)){ + var newScore = (19 - Math.floor(d.final_score / 5)) + if (!scoreArray[newScore]){ + scoreArray[newScore] = 0 + } + scoreArray[newScore] = scoreArray[newScore] + 1 + } + }); + + var width = ($(document).width())/2.5; + var height = ($(document).height())/1.5; + var padding = {left:30, right:30, top:20, bottom:20}; + + var axisName = [] + for(var i = 0; i < scoreArray.length; i++){ + axisName.push( (100-i*5).toString() + "-" + (95-i*5).toString()) + } + + var xScale = d3.scale.ordinal() + .domain(d3.range(scoreArray.length)) + .rangeRoundBands([0, width - padding.left - padding.right]); + + var xScaleName = d3.scale.ordinal() + .domain(axisName) + .rangeRoundBands([0, width - padding.left - padding.right]); + + var yScale = d3.scale.linear() + .domain([0,d3.max(scoreArray)]) + .range([height - padding.top - padding.bottom, 0]); + + var xAxis = d3.svg.axis() + .scale(xScale) + .orient("bottom"); + + var xAxisName = d3.svg.axis() + .scale(xScaleName) + .orient("bottom"); + + var yAxis = d3.svg.axis() + .scale(yScale) + .orient("left"); + + var rectPadding = 4; + + var rects = chartSvg.selectAll(".MyRect") + .data(scoreArray) + .enter() + .append("rect") + .attr("class","MyRect") + .attr("transform","translate(" + padding.left + "," + padding.top + ")") + .attr("x", function(d,i){ + return xScale(i) + rectPadding/2; + } ) + .attr("y",function(d){ + return yScale(d); + }) + .attr("width", xScale.rangeBand() - rectPadding ) + .attr("height", function(d){ + return height - padding.top - padding.bottom - yScale(d); + }); + + var texts = chartSvg.selectAll(".MyText") + .data(scoreArray) + .enter() + .append("text") + .attr("class","MyText") + .attr("transform","translate(" + padding.left + "," + padding.top + ")") + .attr("x", function(d,i){ + return xScale(i) + rectPadding/2; + } ) + .attr("y",function(d){ + return yScale(d); + }) + .attr("dx",function(){ + return (xScale.rangeBand() - rectPadding)/2; + }) + .attr("dy",function(d){ + return 20; + }) + .text(function(d){ + return d; + }); + + chartSvg.append("g") + .attr("class","axis") + .attr("transform","translate(" + padding.left + "," + (height - padding.bottom) + ")") + .call(xAxisName); + + chartSvg.append("g") + .attr("class","axis") + .attr("transform","translate(" + padding.left + "," + padding.top + ")") + .call(yAxis); + } + + function update(source) { + var levelWidth = [1]; + var childCount = function(level, n) { + + if (n.children && n.children.length > 0) { + if (levelWidth.length <= level + 1) levelWidth.push(0); + + levelWidth[level + 1] += n.children.length; + n.children.forEach(function(d) { + childCount(level + 1, d); + }); + } + }; + childCount(0, root); + var newHeight = d3.max(levelWidth) * 30; + tree = tree.size([newHeight, viewerWidth]); + + var nodes = tree.nodes(root).reverse(), + links = tree.links(nodes); + + var maxscore = 0 + var minscore = 100 + var maxMinScore = function(batch_node){ + if (batch_node.children && batch_node.children.length > 0){ + for (var i=0; i maxscore){ + maxscore = batch_node.children[i].final_score + } + if (batch_node.children[i].final_score < minscore){ + minscore = batch_node.children[i].final_score + } + if (batch_node.children[i].children && batch_node.children[i].children.length > 0){ + maxMinScore(batch_node.children[i]) + } + } + } + } + maxMinScore(root) + var scoreRange = maxscore - minscore + + nodes.forEach(function(d) { + d.y = (d.depth * (maxLabelLength * 8.5)); + }); + + node = svgGroup.selectAll("g.node") + .data(nodes, function(d) { + return d.id || (d.id = ++i); + }); + + var nodeEnter = node.enter().append("g") + .attr("class", "node") + .attr("transform", function(d) { + return "translate(" + source.y0 + "," + source.x0 + ")"; + }) + .on('click', click); + + nodeEnter.append("circle") + .attr('class', 'nodeCircle') + .attr("r", 0) + .style("fill", function(d) { + return d._children ? "lightsteelblue" : "#fff"; + }); + + nodeEnter.append("text") + .attr("x", function(d) { + return d.children || d._children ? -10 : 10; + }) + .attr("dy", ".35em") + .attr('class', 'nodeText') + .attr("text-anchor", function(d) { + return d.children || d._children ? "end" : "start"; + }) + .text(function(d) { + return d.name; + }) + .style("fill-opacity", 0); + + node.select('text') + .attr("x", function(d) { + return d.children || d._children ? -10 : 10; + }) + .attr("text-anchor", function(d) { + return d.children || d._children ? "end" : "start"; + }) + .text(function(d) { + return d.name; + }); + + node.select("circle.nodeCircle") + .attr("r", 4.5) + .style("fill", function(d) { + return d._children ? "lightsteelblue" : "#fff"; + }); + + var nodeUpdate = node.transition() + .duration(duration) + .attr("transform", function(d) { + return "translate(" + d.y + "," + d.x + ")"; + }); + + nodeUpdate.select("text") + .style("fill-opacity", 1); + + var nodeExit = node.exit().transition() + .duration(duration) + .attr("transform", function(d) { + return "translate(" + source.y + "," + source.x + ")"; + }) + .remove(); + + nodeExit.select("circle") + .attr("r", 0); + + nodeExit.select("text") + .style("fill-opacity", 0); + + var link = svgGroup.selectAll("path.link") + .data(links, function(d) { + return d.target.id; + }); + + link.enter().insert("path", "g") + .attr("class", "link") + .attr("d", function(d) { + var o = { + x: source.x0, + y: source.y0 + }; + return diagonal({ + source: o, + target: o + }); + }); + + link.transition() + .duration(duration) + .attr("d", diagonal) + .style("stroke-width",function(d){ + return (d.target.final_score - minscore)/scoreRange*4 + 0.5 + }); + + link.exit().transition() + .duration(duration) + .attr("d", function(d) { + var o = { + x: source.x, + y: source.y + }; + return diagonal({ + source: o, + target: o + }); + }) + .remove(); + + nodes.forEach(function(d) { + d.x0 = d.x; + d.y0 = d.y; + }); + + + + node.on("mouseover",function(d){ + if (!(d.children || d._children)){ + tooltip.html("Final score:" + d.final_score + "
" + d.indicator_socre) + .style("left", (d3.event.pageX) + "px") + .style("top", (d3.event.pageY + 20) + "px") + .style("opacity",1.0); + } + else { + if (d.children) {len = d.children.length} else {len = d._children.length} + tooltip.html("Set type:" + d.name + "
" + "Children Num:" + len + "
" + "Max score:" + d.final_score + "
") + .style("left", (d3.event.pageX) + "px") + .style("top", (d3.event.pageY + 20) + "px") + .style("opacity",1.0); + } + }) + .on("mousemove",function(d){ + tooltip.style("left", (d3.event.pageX) + "px") + .style("top", (d3.event.pageY + 20) + "px"); + }) + .on("mouseout",function(d){ + tooltip.style("opacity",0.0); + }); + } + + var svgGroup = baseSvg.append("g"); + + var thresholdText = baseSvg.append("foreignObject") + .attr("class", "externalObject") + .attr("width", 150) + .attr("height", 50) + .attr("transform",function(d){ + return "translate(" + 10 + "," + 0 + ")"; + }) + .append("xhtml:div") + .html( "Final_score_threshold:" + ""); + + baseSvg.select(".externalObject") + .on("keyup",function(d){ + thresholdInitial(root) + thresholdChildren(root,$("#new_x1").val()) + update(root); + centerNode(root); + if (chartflag > 0 ){ + baseSvg.selectAll(".chart").remove() + createChart(root) + } + }) + + var artefactButton = baseSvg.selectAll("text") + .data(allFeatures) + .enter().append("text") + .attr("class","artefact") + .attr("x",0) + .attr("y",80) + .attr("transform",function(d,i){ + return "translate(" + 10 + "," + 30*i + ")"; + }) + .text(function(d) {return ("√ " + d);}) + .on("click",function(d){ + if (allFeatures.indexOf(d) >= 0){ + d3.select(this).attr("class","artefactunselected") + .text(function(d,i){ + return ( "× " + d) + }) + deleteFeatues.push(d) + allFeatures.remove(d) + deleteArtefactLoop(deleteFeatues) + thresholdInitial(root) + thresholdChildren(root,$("#new_x1").val()) + update(root) + } + else { + d3.select(this).attr("class","artefact") + .text(function(d,i){ + return ( "√ " + d) + }) + allFeatures.push(d) + deleteFeatues.remove(d) + deleteArtefactLoop(deleteFeatues) + thresholdInitial(root) + thresholdChildren(root,$("#new_x1").val()) + update(root) + } + if (chartflag > 0 ){ + baseSvg.selectAll(".chart").remove() + createChart(root) + } + }); + + var artefactText = baseSvg.append("text") + .datum("Choose artefacts:") + .attr("x",0) + .attr("y",0) + .attr("transform",function(d){ + return "translate(" + 10 + "," + 60 + ")"; + }) + .text(function(d) {return d;}); + + var thresholdButton = baseSvg.append("text") + .datum(80) + .attr("class","buttontext") + .attr("x",0) + .attr("y",0) + .attr("transform",function(d){ + return "translate(" + 140 + "," + 35 + ")"; + }) + .text(function(d){ + return ("➢") + }) + .on("click",function(d){ + thresholdInitial(root) + thresholdChildren(root,$("#new_x1").val()) + update(root); + if (chartflag > 0 ){ + baseSvg.selectAll(".chart").remove() + createChart(root) + } + }); + + var viewText = baseSvg.append("text") + .datum("Choose views:") + .attr("x",0) + .attr("y",0) + .attr("transform",function(d){ + return "translate(" + 140 + "," + 60 + ")"; + }) + .text(function(d) {return d;}); + + var advanceViewButton = baseSvg.append("text") + .datum("advance") + .attr("class","artefact") + .attr("id","advance") + .attr("x",0) + .attr("y",0) + .attr("transform",function(d){ + return "translate(" + 140 + "," + 80 + ")"; + }) + .text(function(d){ + return ("√ " + d) + }) + .on("click",function(d){ + baseSvg.selectAll(".chart").remove() + chartflag = 0 + svgGroup.attr("display","inline") + d3.select(this).text(function(d){ + return ( "√ " + d) + }) + .attr("class","artefact") + d3.select("#quick").text(function(d){ + return ("× " + d) + }) + .attr("class","artefactunselected") + }); + var quickViewButton = baseSvg.append("text") + .datum("quick") + .attr("class","artefactunselected") + .attr("id","quick") + .attr("x",0) + .attr("y",0) + .attr("transform",function(d){ + return "translate(" + 140 + "," + 110 + ")"; + }) + .text(function(d){ + return ("× " + d) + }) + .on("click",function(d){ + svgGroup.attr("display","none") + createChart(root) + chartflag = 1 + d3.select(this).text(function(d){ + return ( "√ " + d) + }) + .attr("class","artefact") + d3.select("#advance").text(function(d){ + return ("× " + d) + }) + .attr("class","artefactunselected") + }); + + root = treeData; + root.x0 = viewerHeight / 2; + root.y0 = 0; + + function clone(obj) { + if (null == obj || "object" != typeof obj) return obj; + if (obj instanceof Date) { + var copy = new Date(); + copy.setTime(obj.getTime()); + return copy; + } + if (obj instanceof Array) { + var copy = []; + var len = obj.length + for (var i = 0; i < len; ++i) { + copy[i] = clone(obj[i]); + } + return copy; + } + if (obj instanceof Object) { + var copy = {}; + for (var attr in obj) { + if (obj.hasOwnProperty(attr)) copy[attr] = clone(obj[attr]); + } + return copy; + } + } + + var tooltip = d3.select("body") + .append("div") + .attr("class","tooltip") + .style("opacity",0.0); + + newroot=clone(root); + update(root); + centerNode(root); +}); From 7fdae607fb534eed3934e3368ec49321aca9ba1e Mon Sep 17 00:00:00 2001 From: T0day Date: Sat, 26 Aug 2017 04:44:17 +0800 Subject: [PATCH 2/7] The script that generates the json file is taken out separately --- {visualization => final_relationships}/example_data.json | 0 {visualization => final_relationships}/generate_json.js | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename {visualization => final_relationships}/example_data.json (100%) rename {visualization => final_relationships}/generate_json.js (100%) diff --git a/visualization/example_data.json b/final_relationships/example_data.json similarity index 100% rename from visualization/example_data.json rename to final_relationships/example_data.json diff --git a/visualization/generate_json.js b/final_relationships/generate_json.js similarity index 100% rename from visualization/generate_json.js rename to final_relationships/generate_json.js From 79f4ebca39b1a41e8eaccd0da0184d29311fffbb Mon Sep 17 00:00:00 2001 From: T0day Date: Mon, 28 Aug 2017 15:11:58 +0800 Subject: [PATCH 3/7] Add the copyright information --- visualization/relationship_d3.js | 35 ++++++++++++++++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) diff --git a/visualization/relationship_d3.js b/visualization/relationship_d3.js index 88ae183..9fe059e 100644 --- a/visualization/relationship_d3.js +++ b/visualization/relationship_d3.js @@ -1,3 +1,36 @@ +/*Copyright (c) 2013-2016, Rob Schmuecker +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +* The name Rob Schmuecker may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL MICHAEL BOSTOCK BE LIABLE FOR ANY DIRECT, +INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY +OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +Author: Rob Schmuecker +Link: http://bl.ocks.org/robschmuecker/7880033 (D3.js Drag and Drop, Zoomable, Panning, Collapsible Tree with auto-sizing.) +Reference code: Line34- Line127, Line394- Line553 +*/ + + treeJSON = d3.json("example_data.json", function(error, treeData) { var totalNodes = 0; var maxLabelLength = 0; @@ -518,8 +551,6 @@ treeJSON = d3.json("example_data.json", function(error, treeData) { d.y0 = d.y; }); - - node.on("mouseover",function(d){ if (!(d.children || d._children)){ tooltip.html("Final score:" + d.final_score + "
" + d.indicator_socre) From f332670a7119d2e234720e8c2f5a9f0d7ab74688 Mon Sep 17 00:00:00 2001 From: T0day Date: Mon, 28 Aug 2017 16:17:16 +0800 Subject: [PATCH 4/7] Detail about my work and his work --- visualization/relationship_d3.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/visualization/relationship_d3.js b/visualization/relationship_d3.js index 9fe059e..cf52afe 100644 --- a/visualization/relationship_d3.js +++ b/visualization/relationship_d3.js @@ -27,9 +27,12 @@ EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. Author: Rob Schmuecker Link: http://bl.ocks.org/robschmuecker/7880033 (D3.js Drag and Drop, Zoomable, Panning, Collapsible Tree with auto-sizing.) -Reference code: Line34- Line127, Line394- Line553 -*/ +Reference code: Line37- Line130, Line397- Line556 +Detail: My work is based on Rob Schmuecker's work. +I used his code to implement the tree map(Line394- Line553), auto-sizing(Line101- Line112), Zoomable(Line89- Line112) and Collapsible(Line114- Line123) +And my work is to give three views, weighting the score and show tooltips. +*/ treeJSON = d3.json("example_data.json", function(error, treeData) { var totalNodes = 0; From 0da1e0ee473bed3f3fcf77f0334f77f9b54c24f3 Mon Sep 17 00:00:00 2001 From: T0day Date: Mon, 28 Aug 2017 17:01:49 +0800 Subject: [PATCH 5/7] Add credits on the web page MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit “This webpage is reference Rob Schmuecker's work: http://bl.ocks.org/robschmuecker/7880033” Show this sentence in the webpage. --- visualization/index.html | 5 +++++ visualization/relationship_d3.js | 21 +++++++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/visualization/index.html b/visualization/index.html index a089d93..3c526cb 100644 --- a/visualization/index.html +++ b/visualization/index.html @@ -101,6 +101,11 @@ text-anchor: middle; } + .copyrightText { + font-size:9px; + font-family:sans-serif; + } + diff --git a/visualization/relationship_d3.js b/visualization/relationship_d3.js index cf52afe..f27d117 100644 --- a/visualization/relationship_d3.js +++ b/visualization/relationship_d3.js @@ -733,6 +733,27 @@ treeJSON = d3.json("example_data.json", function(error, treeData) { .attr("class","artefactunselected") }); + + var copyrightText = baseSvg.append("text") + .datum(["@ Copyright: This webpage is reference Rob Schmuecker's work:"]) + .attr("class","copyrightText") + .attr("x",0) + .attr("y",0) + .attr("transform",function(d,i){ + return "translate(" + 10 + "," + 200 + ")"; + }) + .text(function(d) {return d;}); + + var copyrightText = baseSvg.append("text") + .datum(["http://bl.ocks.org/robschmuecker/7880033"]) + .attr("class","copyrightText") + .attr("x",0) + .attr("y",0) + .attr("transform",function(d,i){ + return "translate(" + 10 + "," + 215 + ")"; + }) + .text(function(d) {return d;}); + root = treeData; root.x0 = viewerHeight / 2; root.y0 = 0; From fb808dece9265d4351d99a2b2cfd8397d385a36c Mon Sep 17 00:00:00 2001 From: T0day Date: Tue, 29 Aug 2017 22:11:46 +0800 Subject: [PATCH 6/7] Update the description of generating final score script --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d09f835..a7a89e6 100644 --- a/README.md +++ b/README.md @@ -77,7 +77,7 @@ predict | predict mode is used to predict the data without the label. You shou ##### Visualization ###### Generate the random final score -[This](https://github.com/hi-T0day/gsoc_relationship/tree/master/visualization/final_relationships) is an alternative to generating final score data. You just need copy the code in `generate_json.js` to the website [Generate_Json](http://beta.json-generator.com/), and then click the generate button, you will get the JSON file. +Considering that the final score cannot be generated from the primary table temporarily, [this](https://github.com/HolmesProcessing/gsoc_relationship/tree/master/final_relationships) is an alternative to generate final score data randomly. You just need copy the code in `generate_json.js` to the website [Generate_Json](http://beta.json-generator.com/), and then click the generate button, you will get the JSON file that web page can use to display the tree map. ###### Visualizaton page [This](http://120.77.40.25/newd3/index.html) is the web page to visualize the final score relationship. From 6fd1d7a1487a36ce97dbb02ef3f7ad2ff010421e Mon Sep 17 00:00:00 2001 From: T0day Date: Tue, 29 Aug 2017 22:29:42 +0800 Subject: [PATCH 7/7] Delete the readme file in the subdirectory --- ml/README.md | 29 ----------------------------- ml/images/NN-figure.png | Bin 28703 -> 0 bytes visualization/README.md | 32 -------------------------------- 3 files changed, 61 deletions(-) delete mode 100644 ml/README.md delete mode 100644 ml/images/NN-figure.png delete mode 100644 visualization/README.md diff --git a/ml/README.md b/ml/README.md deleted file mode 100644 index 2723c80..0000000 --- a/ml/README.md +++ /dev/null @@ -1,29 +0,0 @@ -# Machine Learning Part for Holmes Relationship - -## OverView -In Spark, we preprocess the data analyzed by Holmes-Totem to get the format data. The format data are used to train a model for classifying the malware in the TensorflowOnSpark. - -### Purpose -We use direct relationship score algorithm to get the final relationship score. The prerequisite for this algorithm is the need to know the label of malware. - -## Pre-Processing Data -#### Virustotal Results -Virustotal gives the malware detection signatures of different vendors. Although different vendors have different naming conventions, the keywords in signatures are the same. We drop the prefixes, suffixes and any extra words except for the keywords. And then, we count the number of occurrences of the keyword and keep only those with the number >= 50. -To a certain malware, we make a loop for the keywords, if this malware has this keyword in its signatures, we assign value 1 to the position of the keyword. Finally, we get a 0-1 list. - -#### KMeans clustering in Virustotal -We use the KMeans algorithm to train the results in the last step, and the 3024 samples are divided into ten categories. - -*Tips: The reason that I only cluster 3024 malware is that 1). even a small percent is still a lot of malware. 2). VT results for many samples are too generic to give any useful information.* - -#### Peinfo -We extract int values and boolean values from the peinfo analysis results. -The int values are entropy, virtual address, virtual size, and size separate in the section .text, .data, .rsrc, and .rdata. In addition, the timestamp, the total size of int values is 17. -We get a dll\_function list by counting the number of dll\_function and keep the only number >= 10000. The size of the list is 502. To a certain malware, we assign true or false when it contains the dll\_function or not. - -#### Objdump -By objdump service, we can get the opcodes of the malware. The file: x86Opcodes contains the normal opcodes. We extract the top 10000 opcodes and convert them into int according to opcodes' sequence in the file. - -## Neural Network -![GitHub Logo](./images/NN-figure.png) -The figure above shows the framework of the neural network. The peinfo data are trained by multi-layer perceptron, while the objdump are trained by convolution neural network. Finally, we concatenate the two one-dimensional vectors and use softmax layer to get the prediction label. diff --git a/ml/images/NN-figure.png b/ml/images/NN-figure.png deleted file mode 100644 index e616b7a56d587794af80b74aab204133ae4c9f9d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 28703 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 diff --git a/visualization/README.md b/visualization/README.md deleted file mode 100644 index 36e13a2..0000000 --- a/visualization/README.md +++ /dev/null @@ -1,32 +0,0 @@ -# Visualization Part for Holmes Relationship - -## OverView -Visualization part is a website displaying the final relationships using d3.js. There are multiple views and tricks to improve the user experience. The page shows the relationships in a tree map. - -[See here](http://120.77.40.25/d3/index.html) - -### Purpose -Visualization can show the data more intuitively, helping the analysts do deep-dive investigations. - -## Multiple Views -###### These three views can be used together. -#### Threshold View -When we set a threshold score, those nodes whose scores are greater than the threshold are displayed. -#### Artefact View -When we click the artefact’s name, the nodes with this feature are added to ( or deleted from ) the map. -#### Quick View -This view shows a histogram that displays the number of nodes in different segments of scores. -## Tricks -#### Collapsible -When we click on the branch node, the branch node’s leaf nodes are folded and also the branch node is filled with steelblue. -#### Zoomable -We can change the map scale by mouse wheel and also translate the map by dragging. -#### Tooltip -When we move the mouse to the leaf node, the tooltip shows the final score, the reason that two samples are related, and the degree of this relation. -When we move the mouse to the branch node, the tooltip shows the branch name, the number of children in this branch, and the maximum score in this branch. -#### Visualization weight -In the whole map, the line weight is set according to the target node score. -In each set, the nodes are sorted by the score. -## Data -At present, the JSON data generated by [JSON GENERATOR](http://beta.json-generator.com), `generate_json.js` is the script to generate the data. -In the future, the JSON data will be generated from the final relationship table.