├── README.md ├── Sysmon.psm1 ├── cluster-scan.ps1 ├── graph-anomaly.ps1 ├── my-log.json ├── my-own-log.json ├── my-sysmon-data.json ├── random-rater ├── show-threat-path ├── sysmon-rta.json ├── threat-graph-vi.ps1 └── threat-graph.ps1 /README.md: -------------------------------------------------------------------------------- 1 | # Sysmon Visualizaton and Tools (work in progress) 2 | A collection of useful PowerShell tools to collect, organize, and visualize Sysmon event data. There's more background 3 | of what we're tring to accomplish in this blog post:https://www.varonis.com/blog/sysmon-and-threat-detection-exploring-the-sysmon-log/ 4 | 5 | To get going with this, you'll need to: 6 | 1. Download Doug Finke's powershell alogithms: https://github.com/dfinke/powershell-algorithms 7 | 2. Install GraphViz and PSGraph: 8 | 9 | #install GraphViz from Chocately 10 | 11 | Find-Package graphviz | Install-Package -ForceBootstrap 12 | 13 | #install module PSGraph 14 | 15 | Find-Module PSGraph | Install-Module 16 | 17 | #import module PSGraph 18 | 19 | Import-Module PSGraph 20 | 21 | 3. And finally install and import the delicious PSQuickGraph wrapper: 22 | 23 | Install-Module -Name PSQuickGraph 24 | 25 | Import-Module PSQuickGraph 26 | 27 | # Then download the Sysmon module and PS scripts 28 | 5. import-module sysmon 29 | 6. . .\threat-graph.ps1 # build $g 30 | 7. .\threat-graph-vi.ps1 # visualize! 31 | 8. Try out threat_search.ps1 and other scripts in repository (random-rater, ..) 32 | -------------------------------------------------------------------------------- /Sysmon.psm1: -------------------------------------------------------------------------------- 1 | function Get-SysmonLogs 2 | { 3 | <# 4 | .Synopsis 5 | Get-SysmonLogs 6 | .DESCRIPTION 7 | This cmd-let will make it possible to get the logs from sysmon which you can filter and search for malicious activity 8 | .EXAMPLE 9 | Get-SysmonLogs 10 | .EXAMPLE 11 | get-SysmonLogs | where {($_.parentImage -like "*office*") -and ($_.CommandLine -like "*powershell*")} 12 | #> 13 | 14 | 15 | 16 | #Get ID (Process start) 17 | 18 | $events = Get-WinEvent -LogName "Microsoft-Windows-Sysmon/Operational" | where { $_.id -eq 1 } 19 | 20 | foreach ($event in $events) { 21 | 22 | $ev = $event.Message -split "`r`n" 23 | 24 | $jsons="{ " 25 | foreach ($line in $ev) { 26 | $line=$line -replace "\\","\\" ` 27 | -replace "\{"," " ` 28 | -replace "\}"," " ` 29 | -replace '"','\"' ` 30 | -replace "`n"," " 31 | 32 | $line=$line -replace '(\s*[\w\s]+):\s*(.*)', '"$1":"$2",' 33 | $jsons = $jsons + $line } 34 | 35 | $jsons =$jsons + '"blah" : "blah" }' 36 | ConvertFrom-Json -InputObject $jsons 37 | } 38 | 39 | 40 | } -------------------------------------------------------------------------------- /cluster-scan.ps1: -------------------------------------------------------------------------------- 1 | . ..\queue\Queue.ps1 2 | #Clustering Algorithm (Xu et. al) 3 | $epsil = .1 4 | $mu = 2 5 | 6 | function v-index ( $n) { 7 | 8 | for ($i=0; $i -lt $g.vertices.count; $i++) { 9 | 10 | if($g.vertices[$i].value.Key -eq $n) { return $i} 11 | 12 | } 13 | 14 | return -1 15 | } 16 | 17 | #count neighbors in common 18 | function friend-count ($i, $j) { 19 | $vi = $A[$i] 20 | 21 | $count=0 22 | if ($vi -eq $null){ return 0} 23 | for($k=0; $k -lt $vi.count; $k++) { 24 | if ($A[$vi[$k]] -contains $j) { #got one 25 | $count++ 26 | } 27 | } 28 | write-host "Friend count " $i "|" $j "|" $count 29 | 30 | return $count 31 | 32 | 33 | } 34 | 35 | function jacquard($s, $e) { #jacquard metric 36 | $ex = v-index $e.value.Key 37 | $sx = v-index $s.value.Key 38 | if($s.value.EdgeCnt -eq 0) { return 0} 39 | $v=(friend-count $sx $ex) /$s.vaLue.EdgeCnt 40 | 41 | return $v 42 | 43 | } 44 | 45 | function seed ($v ) { 46 | 47 | 48 | $sn = $v.value.Key 49 | $sx = v-index $sn 50 | #now we just have to see if $v has ge mu neighbors with a greater than mu value 51 | foreach ($e in $v.getNeighbors()) { 52 | 53 | if ( jacquard $v $e -ge $mu) {return $true } 54 | } 55 | 56 | return $false 57 | 58 | } 59 | 60 | 61 | 62 | function dir-reach ($v) { 63 | $r=@() 64 | 65 | foreach ($e in $v.getNeighbors()) { 66 | if($e.value.Visited -eq 1) {continue} 67 | if ( jacquard $v $e -ge $mu) { 68 | $r+=$e 69 | } 70 | 71 | } 72 | return $r 73 | } 74 | 75 | #Let's create a sparse adjacency matrix for the heck of it :-) 76 | $A=@{} 77 | 78 | foreach ($v in $g.vertices.Keys) { 79 | $start = $g.vertices[$v] 80 | #lets build a row 81 | $row=@() 82 | $sx= v-index $start.value.Key 83 | $ecnt=0 84 | foreach($n in $start.getNeighbors()) { 85 | #get index 86 | $ex = v-index $n.value.Key 87 | if($sx -eq $ex) {continue} 88 | if( $ex -ge 0 ) { 89 | $row+=$ex 90 | $ecnt++ 91 | } 92 | } 93 | $A[$sx] = $row 94 | $start.value.EdgeCnt = $ecnt #lets put edge/neighbor count on vertex 95 | } 96 | 97 | 98 | 99 | 100 | 101 | #Let's Identify Clusters and mark bridges/hubs 102 | 103 | 104 | #load the queue 105 | $unclassQueue = New-Object Queue 106 | $clustercnt=1 107 | foreach ($k in $g.vertices.Keys) { 108 | 109 | $v=$g.vertices[$K] 110 | if($v.value.Visted -eq 1) {continue} 111 | $v.value.Visited=1 112 | if (seed $v) { 113 | #now lets extend the seed 114 | $clustercnt++ 115 | $v.value.Cluster = "Cluster$($clustercnt))" 116 | $r=dir-reach $v 117 | #load the queue 118 | for($i=0;$i -lt $r.count;$i++) { 119 | $unclassQueue.enqueue($r[$i]) 120 | } 121 | while (!$unclassQueue.isempty()) { 122 | $currentVertex = $unclassQueue.dequeue() 123 | if( $currentVertex.Cluster -eq "") {$currentVertex.value.Cluster = "Cluster$($clustercnt))" } 124 | #add to cluster 125 | $r=dir-reach $currentVertex 126 | for($i=0;$i -lt $r.count;$i++) { 127 | $unclassQueue.enqueue($r[$i]) 128 | } 129 | } 130 | } 131 | 132 | 133 | } -------------------------------------------------------------------------------- /graph-anomaly.ps1: -------------------------------------------------------------------------------- 1 | . ..\queue\Queue.ps1 2 | #find anomalies - variation of BADGraph 3 | 4 | 5 | function extend-subgraph($v, $t) { 6 | 7 | 8 | $vertexQueue = New-Object Queue 9 | $vertexQueue.enqueue($v) 10 | $h=$v.value.Weight 11 | $s=@() #subgraph 12 | $s+=$v 13 | 14 | while (!$vertexQueue.isEmpty()) { 15 | $currentVertex = $vertexQueue.dequeue() 16 | 17 | $es= $currentVertex.getEdges() 18 | $extend=$false 19 | foreach($e in $es) { 20 | 21 | $ev= $e.endVertex 22 | 23 | if ( ($h+ $ev.value.weight)/($s.count+1) -le $th ) { 24 | $s+=$ev 25 | $h =$h + $ev.value.weight 26 | 27 | #queue it up 28 | $vertexQueue.enqueue($ev) 29 | } 30 | 31 | } 32 | 33 | } 34 | if($s.count -ge 2) { 35 | $global:mset.Add($s)|Out-Null 36 | } 37 | 38 | 39 | 40 | } 41 | 42 | 43 | 44 | $AW=0 45 | $GW=0 46 | #$ms = @() #list of abnormal sub-graphs 47 | $mset = [System.Collections.ArrayList]@() 48 | 49 | #calculate total "weight" 50 | foreach ($e in $g.getAllEdges() ) { 51 | $GW = $GW + $e.weight 52 | } 53 | 54 | write-host "Weight of Graph: " $GW 55 | $AW = $GW / $g.vertices.count 56 | write-host "Average weight per vertex: " $AW 57 | 58 | 59 | #assign weight to vertices 60 | for ($i=0; $i -lt $g.vertices.count; $i++) { 61 | 62 | $w=0 63 | $v=$g.vertices[$i] 64 | foreach($e in $v.getEdges()) { 65 | if($e -eq $null) {continue} 66 | $w=$w + $e.weight 67 | } 68 | $v.value.Weight = $w 69 | 70 | } 71 | 72 | 73 | #Lets hunt for anomalies 74 | $th=[single]($AW)*3 #threshold value 75 | 76 | foreach ($k in $g.vertices.Keys) { 77 | 78 | 79 | $v=$g.vertices[$k] 80 | #worthy candidates 81 | extend-subgraph $v $th 82 | 83 | } 84 | 85 | for($i=0; $i -lt $mset.count; $i++) { 86 | write-host "---Subgraph" $i 87 | $w=0 88 | for($j=0; $j -lt $mset[$i].count; $j++) { 89 | write-host "------ "$mset[$i][$j].value.Key 90 | $w=$mset[$i][$j].value.Weight + $w 91 | 92 | } 93 | $a=$w/$mset[$i].count 94 | write-host "------ " "Weight:" $w "Average weight:" $a 95 | } 96 | 97 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /random-rater: -------------------------------------------------------------------------------- 1 | #random walk 2 | 3 | #borrowed from the Intertoobz 4 | function mult-matrix{ # just doing a left multiply of v*M 5 | param ( 6 | $v, [hashtable]$m 7 | ) 8 | 9 | $nc,$mc,$pc = ($v.count - 1), ($m.count - 1), ($m[0].count -1) 10 | if ($v.count -ne $m.count) {throw "Multiplication impossible"} 11 | $vn = @(0)*($m[0].count) 12 | foreach ($i in 0..$nc) { 13 | foreach ($j in 0..$pc) { 14 | $sum = 0 15 | foreach ($k in 0..$mc){$sum += $m[$k][$i]*$v[$k]} 16 | 17 | } 18 | $vn[$i]=$sum 19 | } 20 | $vn 21 | } 22 | 23 | 24 | 25 | function v-index ( $n) { 26 | 27 | for ($i=0; $i -lt $g.vertices.count; $i++) { 28 | 29 | if($g.vertices[$i].value.Key -eq $n) { return $i} 30 | 31 | } 32 | 33 | return -1 34 | } 35 | 36 | 37 | $T = @{} #transition matrix 38 | 39 | for($ix=0; $ix -lt $g.vertices.count; $ix++) { 40 | $start = $g.vertices[$ix] 41 | #lets build a row 42 | $row= @(0)*$g.vertices.count #initialize to 0 43 | 44 | 45 | $w=0 46 | foreach($e in $start.getEdges()) { 47 | $w+=$e.weight 48 | } 49 | if ($w -eq 0) { 50 | $row[$ix] =1 #lets keep it looping until it's restarted :-) 51 | 52 | } 53 | else { 54 | #now create transition probability 55 | foreach($e in $start.getEdges()) { 56 | $ev = $e.endVertex 57 | $p = $e.weight 58 | $jx = v-index $ev.value.Key 59 | $row[$jx]= $p/$w 60 | } 61 | } 62 | 63 | $T[$ix] = $row 64 | 65 | } 66 | 67 | 68 | #now let's iterate the matrix using the random walk with restart formula: p=c(pM) + (1-c)u 69 | $vn=@(0)*$g.vertices.count # $g from the threat-graph.ps1 script 70 | if(($sx=v-index $args[0]) -lt 0) { $sx=v-index "PowerShell.exe"} 71 | 72 | $vn[$sx]=1 #initial starting point 73 | $c=.7 # probablity of wandering, feel free to adjust 74 | 75 | 76 | 77 | for($i=0;$i -lt 20; $i++) { #should be enough -:) 78 | $vn = (mult-matrix $vn $T) 79 | #now adjust probabilites by $c = probility of not returning 80 | $vn = $vn|% {$_*$c} #multiply by vector by $c :-) 81 | #now add in return probability 82 | $vn[$sx] = $vn[$sx] + (1-$c) 83 | } 84 | 85 | #lets print out say top 10 86 | #first turn vn into an array of object {index of vector, probability} 87 | $objs= @{} 88 | for($i=0;$i -lt $vn.count;$i++) { 89 | 90 | $objs.Add($i,$vn[$i]) 91 | 92 | } 93 | 94 | $sorted = $objs.getEnumerator()|sort Value 95 | # print out first en 96 | write-host "Possible Threats based on start node: $($($g.vertices[$sx].value.Key))" 97 | write-host " Ranking" 98 | 99 | #get first five that are positive 100 | $count =0 101 | $i =0 102 | While ($count -lt 10 -and $i -lt $g.vertices.count){ 103 | if ($sorted[$i].Value -eq 0) {$i++; continue} 104 | write-host "----" $g.vertices[$i].value.Key 105 | write-host "---------- $($sorted[$i].Value)" 106 | $count++ 107 | $i++ 108 | } 109 | 110 | 111 | 112 | -------------------------------------------------------------------------------- /show-threat-path: -------------------------------------------------------------------------------- 1 | #DepthSearch 2 | function depfs ($i) { 3 | 4 | if ($Vertices[$i] -eq $null) { return} 5 | else { 6 | write-host "----" $Vertices[$i].value.Key "|" $Vertices[$i].value.Cline "[$i]" 7 | 8 | depfs $Vertices[$i].value.PPid 9 | } 10 | } 11 | 12 | 13 | 14 | if ($args.count -eq 0) {return} 15 | $name = $args[0] 16 | foreach ($v in $Vertices.Keys) { 17 | 18 | if ($Vertices[$v].value.Key -eq $name) { 19 | write-host `n `n 20 | write-host "-Threat path: " $name "[$($Vertices[$v].value.Pid)]" 21 | depfs $Vertices[$v].value.PPid 22 | } 23 | } 24 | 25 | 26 | -------------------------------------------------------------------------------- /threat-graph-vi.ps1: -------------------------------------------------------------------------------- 1 | #Let's graph it using PSQuickGraph !!! 2 | $gv = New-Graph -Type BiDirectionalGraph 3 | 4 | 5 | 6 | foreach ($e in $g.getAllEdges() ) { 7 | $vs= $e.startvertex 8 | $ve= $e.endvertex 9 | PSQuickGraph\Add-Edge -From $vs.value.Key -To $ve.value.Key -Graph $gv |Out-Null 10 | 11 | } 12 | 13 | Show-GraphLayout -Graph $gv 14 | 15 | -------------------------------------------------------------------------------- /threat-graph.ps1: -------------------------------------------------------------------------------- 1 | #need to donwload Doug Finke's PS algorithms 2 | . .\Graph.ps1 3 | . .\GraphVertex.ps1 4 | . .\GraphEdge.ps1 5 | 6 | $g = New-Object Graph 1 7 | $Vertices = @{} #contains linked list of process hieararchies 8 | 9 | get-sysmonlogs| %{ 10 | $obj= New-Object -TypeName psobject 11 | if ($_.OriginalFileName -eq "?") { #fill in name from Image 12 | $key=$_.Image.Substring($_.Image.lastIndexOf('\') + 1) 13 | $obj |add-Member -MemberType NoteProperty -Name Key -Value $key 14 | } 15 | else {$obj |add-Member -MemberType NoteProperty -Name Key -Value $_.OriginalFileName} 16 | $obj |add-Member -MemberType NoteProperty -Name Pid -Value $_.ProcessId 17 | $obj |add-Member -MemberType NoteProperty -Name PPid -Value $_.ParentProcessId 18 | $obj |add-Member -MemberType NoteProperty -Name Weight -Value 0 19 | $obj |add-Member -MemberType NoteProperty -Name Cluster -Value "" 20 | $obj| add-Member -MemberType NoteProperty -Name EdgeCnt -Value 0 21 | $obj| add-Member -MemberType NoteProperty -Name Visited -Value 0 22 | 23 | if (!$_.CommandLine -contains $_.OriginalFileName) { 24 | $cl = $_.OriginalFileName +" " + $_.CommandLine 25 | $obj |add-Member -MemberType NoteProperty -Name Cline -Value $cl 26 | } 27 | else {$obj |add-Member -MemberType NoteProperty -Name Cline -Value $_.CommandLine} 28 | $pkey = $_.ParentImage.Substring($_.ParentImage.lastIndexOf('\') + 1) 29 | $obj |add-Member -MemberType NoteProperty -Name PKey -Value $pkey 30 | $obj |add-Member -MemberType NoteProperty -Name User -Value $_.User 31 | 32 | $Vertex = New-Object GraphVertex $obj 33 | $g.addVertex($Vertex)|Out-Null 34 | 35 | # create "linked list" based on PIDs 36 | if($Vertices[[string]$_.ProcessId] -eq $null) {$Vertices.Add($_.ProcessId,$Vertex)|Out-Null } } 37 | 38 | 39 | #now build edges 40 | foreach ($v in $g.vertices.Keys) { 41 | $start = $Vertices[$v].value.Key 42 | $end = $Vertices[$v].value.PKey 43 | 44 | #now convert to G world 45 | $end = $g.vertices[$end] 46 | $start = $g.vertices[$start] 47 | 48 | 49 | if( $start -ne $null ) { 50 | 51 | if ($start.findEdge($end) -eq $null) { 52 | $edge = New-Object GraphEdge $start,$end,1 53 | $g.AddEdge($edge)|Out-Null 54 | } 55 | else { 56 | $e= $start.findEdge($end) 57 | $e.weight = $e.weight +1 58 | } 59 | } 60 | 61 | } 62 | 63 | 64 | --------------------------------------------------------------------------------