├── README.md ├── Sysmon Monitoring different way.pdf ├── docker ├── docker-compose.yml └── www │ └── index.html ├── examples ├── dnscat2.txt ├── msf-custom.txt └── msf.txt ├── index.html ├── screenshot └── screenshot.png └── sysgraph.ps1 /README.md: -------------------------------------------------------------------------------- 1 | # SysmonGraph 2 | 3 | Sysmon Graph is project to visualize sysmon logs. 4 | 5 | ![](https://github.com/spyx/SysmonGrahp/blob/main/screenshot/screenshot.png?raw=true) 6 | 7 | You can quickly get overview of relations between processes, file creation, DNS requests or Network connection. 8 | It should help speed process our with manual threat hunting. 9 | 10 | ### Instalation 11 | 12 | ##### Option 1. 13 | 14 | Download project. Navigate to docker file and lunch this command. 15 | 16 | ```bash 17 | docker-compose up 18 | ``` 19 | 20 | Docker will create 2 instances. Neo4J and Web server to server our front UI on port 8888 21 | 22 | ##### Option 2. 23 | 24 | Just lunch this docker command to create noe4j container and open index.html from docker/www in your browser. 25 | 26 | ##### Option 3. 27 | 28 | If you host Noe4J Database just edit index.html file with proper IP addresses. 29 | 30 | 31 | ### Collect Logs 32 | 33 | For collection logs there is simple powershell script. Script simply output all results to screen. If you require collect more logs you can change it at begginig of file. By default script collect process createtion, DNS request, file creation and network conncetion. All Sysmon-ID/Nodes are available. 34 | 35 | ```powershell 36 | sysgraph.ps1 > logs.txt 37 | ``` 38 | 39 | ### Usage 40 | 41 | If you are using first option just navigate to http://localhost:8888 or open index.html. It provide simple UI to see all possible relation between logs. All syslog information are also available for each node 42 | inside our UI. Folder example contains example I used in BSides Talk. Project still in alpha stage. 43 | 44 | 45 | -------------------------------------------------------------------------------- /Sysmon Monitoring different way.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spyx/SysmonGrahp/511f2d93c7e87ccb880c6dcbb7d431ececbb1503/Sysmon Monitoring different way.pdf -------------------------------------------------------------------------------- /docker/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | 3 | services: 4 | web: 5 | image: nginx:alpine 6 | ports: 7 | - 8888:80 8 | volumes: 9 | - ./www:/usr/share/nginx/html 10 | neo4j: 11 | image: neo4j:latest 12 | ports: 13 | - 7474:7474 14 | - 7687:7687 15 | environment: 16 | - NEO4J_AUTH=none 17 | - NEO4J_dbms_memory_pagecache_size=2G 18 | - NEO4J_dbms.memory.heap.initial_size=2G 19 | - NEO4J_dbms_memory_heap_max__size=2G 20 | -------------------------------------------------------------------------------- /docker/www/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | SysmonGraps 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 24 | 25 | 26 | 27 |
28 |
29 |
30 | 31 | 32 | 33 |
34 |
Max Nodes
35 |
300
36 |
37 | 38 |
39 |
Search:
40 |
41 |
42 |
43 |
44 | 47 |
48 |
49 | 50 |
51 |
52 | 53 |
54 |
55 |
56 |
57 |
58 |
59 | 62 |
63 |
64 |
65 |
66 | 67 | 68 |
69 |
70 |
71 |
72 |
73 |
74 | 77 |
78 |
79 |
80 |
81 | 82 |
83 |
84 |
85 |
86 | 87 | 88 | 89 |
90 |
91 |
92 | 93 | 94 | 95 | 96 | 97 | 131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
Details
139 |
140 |
141 |
142 |
143 | 144 | 474 | 475 | 476 | -------------------------------------------------------------------------------- /examples/dnscat2.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spyx/SysmonGrahp/511f2d93c7e87ccb880c6dcbb7d431ececbb1503/examples/dnscat2.txt -------------------------------------------------------------------------------- /examples/msf-custom.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spyx/SysmonGrahp/511f2d93c7e87ccb880c6dcbb7d431ececbb1503/examples/msf-custom.txt -------------------------------------------------------------------------------- /examples/msf.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spyx/SysmonGrahp/511f2d93c7e87ccb880c6dcbb7d431ececbb1503/examples/msf.txt -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | SysmonGraps 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 24 | 25 | 26 | 27 |
28 |
29 |
30 | 31 | 32 | 33 |
34 |
Max Nodes
35 |
300
36 |
37 | 38 |
39 |
Search:
40 |
41 |
42 |
43 |
44 | 47 |
48 |
49 | 50 |
51 |
52 | 53 |
54 |
55 |
56 |
57 |
58 |
59 | 62 |
63 |
64 |
65 |
66 | 67 | 68 |
69 |
70 |
71 |
72 |
73 |
74 | 77 |
78 |
79 |
80 |
81 | 82 |
83 |
84 |
85 |
86 | 87 | 88 | 89 |
90 |
91 |
92 | 93 | 94 | 95 | 96 | 97 | 131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
Details
139 |
140 |
141 |
142 |
143 | 144 | 474 | 475 | 476 | -------------------------------------------------------------------------------- /screenshot/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spyx/SysmonGrahp/511f2d93c7e87ccb880c6dcbb7d431ececbb1503/screenshot/screenshot.png -------------------------------------------------------------------------------- /sysgraph.ps1: -------------------------------------------------------------------------------- 1 | # ID's 2 | # 1 - create process 3 | # 3 - network connection 4 | # 22 -dns 5 | # 11 - file creation 6 | # 15 - file stream creation 7 | # 13 - registry changed 8 | # 12 - registry added 9 | # 7 - image loaded 10 | # 5 - process terminated 11 | 12 | $filter =@{ 13 | Logname='Microsoft-Windows-Sysmon/Operational' 14 | ID=1,3,11,15,22 15 | StartTime = [datetime]::Today 16 | EndTime = [datetime]::Today.addDays(1) 17 | } 18 | 19 | $a = Get-WinEvent -FilterHashtable $filter -ErrorAction SilentlyContinue 20 | if ($a.count -eq 0) { 21 | Write-Host "There is no events to match you filter" 22 | exit 23 | } 24 | 25 | 26 | $pids = @() 27 | $files = $a | Sort-Object -Property Id 28 | $results = "" 29 | $processes = $files | ? {$_.Id -eq 1} 30 | 31 | 32 | Write ("CREATE ") 33 | foreach ($event in $files) { 34 | if ($event.id -eq 1) { 35 | #write ("process") 36 | $ev = $event.Message -split "`r`n" 37 | $jsons="{ " 38 | foreach ($line in $ev) { 39 | $line=$line -replace "\\","\\" ` 40 | -replace "\{"," " ` 41 | -replace "\}"," " ` 42 | -replace '"','\"' ` 43 | -replace "`n"," " 44 | $line=$line -replace '(\s*[\w\s]+):\s*(.*)', '"$1":"$2",' 45 | $jsons = $jsons + $line } 46 | $jsons =$jsons + '"blah" : "blah" }' 47 | 48 | $convert = ConvertFrom-Json -InputObject $jsons 49 | # get values from processes 50 | $UtcTime = $convert.UtcTime 51 | $date = $UtcTime.Split(" ")[0] 52 | $procesID = $convert.ProcessId 53 | $Image = $convert.Image.Replace("\","\\") 54 | $FileVersion = $convert.FileVersion 55 | $Description = $convert.Description 56 | $Product = $convert.Product 57 | $Company = $convert.Company 58 | $OriginalFileName = if ($convert.OriginalFileName -eq '-') {"Unknown"} else {$convert.OriginalFileName} 59 | $CommandLine = $convert.CommandLine 60 | $Bytes = [System.Text.Encoding]::Unicode.GetBytes($CommandLine) 61 | $EncodedCommand = [Convert]::ToBase64String($Bytes) 62 | 63 | $CurrentDirectory = $convert.CurrentDirectory.Replace("\","\\") 64 | $User = $convert.User.Replace("\","\\") 65 | $Hashes = $convert.Hashes 66 | 67 | $ParentImage = $convert.ParentImage 68 | $ParentProcessId = $convert.ParentProcessId 69 | $ParentCommandLine = $convert.ParentCommandLine.Replace("\","\\") 70 | 71 | 72 | 73 | 74 | 75 | if($pids -notcontains $procesID) { 76 | Write "(id$procesID :Process{id:$procesID,name:'$OriginalFileName',user:'$User',date:'$date', CurrentDirectory:'$CurrentDirectory',FileVersion:'$FileVersion',hashes:'$Hashes',UtcTime:'$UtcTime',Image:'$Image',Description:'$Description',Product:'$Product',Company:'$Company', CommandLine:'$EncodedCommand'})," 77 | $pids += $procesID 78 | } 79 | if ($pids -notcontains $ParentProcessId){ 80 | 81 | foreach ($process in $processes) { 82 | if($process.properties[3].value -eq $ParentProcessId){ 83 | $UtcTime = $process.properties[1].value 84 | $date = $UtcTime.Split(" ")[0] 85 | $processID = $process.properties[3].value 86 | $Image = $process.properties[4].value.Replace("\","\\") 87 | $FileVersion = $process.properties[5].value 88 | $Description = $process.properties[6].value 89 | $Product = $process.properties[7].value 90 | $Company = $process.properties[8].value 91 | $OriginalFileName = $process.properties[9].value 92 | $CommandLine = $process.properties[10].value 93 | $Bytes = [System.Text.Encoding]::Unicode.GetBytes($CommandLine) 94 | $EncodedCommand = [Convert]::ToBase64String($Bytes) 95 | $CurrentDirectory = $process.properties[11].value.Replace("\","\\") 96 | $User = $process.properties[12].value.Replace("\","\\") 97 | $Hashes = $process.properties[17].value 98 | 99 | Write "(id$ParentProcessId :Process{id:$procesID,name:'$OriginalFileName',user:'$User',date:'$date', CurrentDirectory:'$CurrentDirectory',FileVersion:'$FileVersion',hashes:'$Hashes',UtcTime:'$UtcTime',Image:'$Image',Description:'$Description',Product:'$Product',Company:'$Company', CommandLine:'$EncodedCommand'})," 100 | $pids += $ParentProcessId 101 | $results += "(id$ParentProcessId)-[:CreateProcess]->(id$procesID)," 102 | break 103 | } 104 | 105 | } 106 | if ($pids -notcontains $ParentProcessId) { 107 | $name = $ParentImage.split("\")[-1] 108 | $results += "(id$ParentProcessId :Process{id:$ParentProcessId,name:'$name',ParentCommandLine:'$ParentCommandLine'})," 109 | $pids += $ParentProcessId 110 | 111 | } 112 | 113 | 114 | 115 | } 116 | $results += "(id$ParentProcessId)-[:CreateProcess]->(id$procesID)," 117 | } 118 | elseif ($event.id -eq 3) { 119 | #write("network") 120 | $ev = $event.Message -split "`r`n" 121 | $jsons="{ " 122 | foreach ($line in $ev) { 123 | $line=$line -replace "\\","\\" ` 124 | -replace "\{"," " ` 125 | -replace "\}"," " ` 126 | -replace '"','\"' ` 127 | -replace "`n"," " 128 | $line=$line -replace '(\s*[\w\s]+):\s*(.*)', '"$1":"$2",' 129 | $jsons = $jsons + $line } 130 | $jsons =$jsons + '"blah" : "blah" }' 131 | #ConvertFrom-Json -InputObject $jsons 132 | $UtcTime = $convert.UtcTime 133 | $convert = ConvertFrom-Json -InputObject $jsons 134 | $ProcessId = $convert.ProcessId 135 | $Image = $convert.Image.Replace("\","\\") 136 | $User = $convert.User 137 | $Protocol = $convert.Protocol 138 | $SourceIp = $convert.SourceIp 139 | $SourceHostname = $convert.SourceHostname 140 | $SourcePort = $convert.SourcePort 141 | $SourcePortName = $convert.SourcePortName 142 | $DestinationIp = $convert.DestinationIp 143 | $DestinationHostname = $convert.DestinationHostname 144 | $DestinationPort = $convert.DestinationPort 145 | $DestinationPortName = $convert.DestinationPortName 146 | $results += "(id$ProcessId)-[:NetworkConnection{utctime:'$UtcTime'}]->(nc$counter :Network{DestinationPortName:'$DestinationPortName', DestinationPort:'$DestinationPort', SourcePortName:'$SourcePortName', SourcePort:'$SourcePort', SourceHostname:'$SourceHostname', SourceIp:'$SourceIp', Protocol:'$Protocol', User:'$User', Image:'$Image', name:'$DestinationIp', DestinationIp:'$DestinationIp',DestinationHostname:'$DestinationHostname' })," 147 | $counter += 1 148 | } 149 | elseif($event.id -eq 22) { 150 | #write("DNS") 151 | $ev = $event.Message -split "`r`n" 152 | $jsons="{ " 153 | foreach ($line in $ev) { 154 | $line=$line -replace "\\","\\" ` 155 | -replace "\{"," " ` 156 | -replace "\}"," " ` 157 | -replace '"','\"' ` 158 | -replace "`n"," " 159 | $line=$line -replace '(\s*[\w\s]+):\s*(.*)', '"$1":"$2",' 160 | $jsons = $jsons + $line } 161 | $jsons =$jsons + '"blah" : "blah" }' 162 | #ConvertFrom-Json -InputObject $jsons 163 | $convert = ConvertFrom-Json -InputObject $jsons 164 | $UtcTime = $convert.UtcTime 165 | $ProcessId = $convert.ProcessId 166 | $QueryName = $convert.QueryName 167 | $QueryStatus = $convert.QueryStatus 168 | $QueryResults = $convert.QueryResults 169 | $Image = $convert.Image.Replace("\","\\") 170 | 171 | $results += "(id$ProcessId)-[:DNS_Request{UtcTime:'UtcTime'}]->(dns$counter :DNS{name:'$QueryName',QueryStatus:'$QueryStatus', QueryResults:'$QueryResults',Image:'$Image'})," 172 | $counter+=1 173 | } 174 | elseif($event.id -eq 11){ 175 | 176 | $ev = $event.Message -split "`r`n" 177 | $jsons="{ " 178 | foreach ($line in $ev) { 179 | $line=$line -replace "\\","\\" ` 180 | -replace "\{"," " ` 181 | -replace "\}"," " ` 182 | -replace '"','\"' ` 183 | -replace "`n"," " 184 | $line=$line -replace '(\s*[\w\s]+):\s*(.*)', '"$1":"$2",' 185 | $jsons = $jsons + $line } 186 | $jsons =$jsons + '"blah" : "blah" }' 187 | #ConvertFrom-Json -InputObject $jsons 188 | $convert = ConvertFrom-Json -InputObject $jsons 189 | 190 | $UtcTime = $convert.UtcTime 191 | $ProcessId = $convert.ProcessId 192 | $Image = $convert.Image.Split("\")[-1] 193 | $TargetFilename = $convert.TargetFilename.Replace("\","\\") 194 | $name = $convert.TargetFilename.Split("\")[-1] 195 | if ($pids -notcontains $ProcessId) { 196 | $results += "(id$ProcessId :Process{id:'$ProcessId',name:'$Image'})-[:FileCreated{UtcTime:'$UtcTime'}]->(file$counter :File{name:'$name', TargetFilename:'$TargetFilename',image:'$Image'})," 197 | $pids += $ProcessId 198 | } 199 | else { 200 | $results += "(id$ProcessId)-[:FileCreated{UtcTime:'$UtcTime'}]->(file$counter :File{name:'$name', TargetFilename:'$TargetFilename',image:'$Image'})," 201 | } 202 | 203 | $counter += 1 204 | 205 | } 206 | elseif($event.id -eq 23){ 207 | 208 | $ev = $event.Message -split "`r`n" 209 | $jsons="{ " 210 | foreach ($line in $ev) { 211 | $line=$line -replace "\\","\\" ` 212 | -replace "\{"," " ` 213 | -replace "\}"," " ` 214 | -replace '"','\"' ` 215 | -replace "`n"," " 216 | $line=$line -replace '(\s*[\w\s]+):\s*(.*)', '"$1":"$2",' 217 | $jsons = $jsons + $line } 218 | $jsons =$jsons + '"blah" : "blah" }' 219 | #ConvertFrom-Json -InputObject $jsons 220 | $convert = ConvertFrom-Json -InputObject $jsons 221 | 222 | $ProcessId = $convert.ProcessId 223 | $Image = $convert.Image.Split("\")[-1] 224 | $TargetFilename = $convert.TargetFilename.Split("\")[-1] 225 | 226 | if ($pids -notcontains $ProcessId) { 227 | $results += "(id$ProcessId :Process{id:'$ProcessId',name:'$Image'})-[:FileDeleted]->(file$counter :FileDel{name:'$TargetFilename'})," 228 | $pids += $ProcessId 229 | } 230 | else { 231 | $results += "(id$ProcessId)-[:FileDeleted]->(file$counter :FileDel{name:'$TargetFilename'})," 232 | } 233 | 234 | 235 | 236 | $counter += 1 237 | 238 | } 239 | elseif($event.id -eq 15){ 240 | 241 | $ev = $event.Message -split "`r`n" 242 | $jsons="{ " 243 | foreach ($line in $ev) { 244 | $line=$line -replace "\\","\\" ` 245 | -replace "\{"," " ` 246 | -replace "\}"," " ` 247 | -replace '"','\"' ` 248 | -replace "`n"," " 249 | $line=$line -replace '(\s*[\w\s]+):\s*(.*)', '"$1":"$2",' 250 | $jsons = $jsons + $line } 251 | $jsons =$jsons + '"blah" : "blah" }' 252 | #ConvertFrom-Json -InputObject $jsons 253 | $convert = ConvertFrom-Json -InputObject $jsons 254 | $UtcTime = $convert.UtcTime 255 | $ProcessId = $convert.ProcessId 256 | $Image = $convert.Image.Split("\")[-1] 257 | $Name = $convert.TargetFilename.Split("\")[-1] 258 | $TargetFilename = $convert.TargetFilename.Replace("\","\\") 259 | $Hash = $convert.Hash 260 | if ($pids -notcontains $ProcessId) { 261 | $results += "(id$ProcessId :Process{id:'$ProcessId',name:'$Image'})-[:FileStreamCreated{UtcTime:'$UtcTime'}]->(file$counter :FileStream{name:'$Name',TargetFilename:'$TargetFilename',Hash:'$Hash'})," 262 | $pids += $ProcessId 263 | } 264 | else { 265 | $results += "(id$ProcessId)-[:FileStreamCreated{UtcTime:'$UtcTime'}]->(file$counter :FileStream{name:'$Name',TargetFilename:'$TargetFilename',Hash:'$Hash'})," 266 | } 267 | $counter += 1 268 | } 269 | elseif($event.id -eq 12){ 270 | 271 | $ev = $event.Message -split "`r`n" 272 | $jsons="{ " 273 | foreach ($line in $ev) { 274 | $line=$line -replace "\\","\\" ` 275 | -replace "\{"," " ` 276 | -replace "\}"," " ` 277 | -replace '"','\"' ` 278 | -replace "`n"," " 279 | $line=$line -replace '(\s*[\w\s]+):\s*(.*)', '"$1":"$2",' 280 | $jsons = $jsons + $line } 281 | $jsons =$jsons + '"blah" : "blah" }' 282 | #ConvertFrom-Json -InputObject $jsons 283 | $convert = ConvertFrom-Json -InputObject $jsons 284 | $Image = $convert.Image.Split("\")[-1] 285 | $TargetObject = $convert.TargetObject.Replace("\","\\") 286 | $ProcessId = $convert.ProcessId 287 | if ($pids -contains $ProcessId) { 288 | $results += "(id$ProcessId)-[:RegistryAddedDeleted]->(registry$couter :Registry{name:'$Image',TargetObject:'$TargetObject'}), " 289 | } 290 | 291 | $counter += 1 292 | } 293 | elseif($event.id -eq 13){ 294 | 295 | $ev = $event.Message -split "`r`n" 296 | $jsons="{ " 297 | foreach ($line in $ev) { 298 | $line=$line -replace "\\","\\" ` 299 | -replace "\{"," " ` 300 | -replace "\}"," " ` 301 | -replace '"','\"' ` 302 | -replace "`n"," " 303 | $line=$line -replace '(\s*[\w\s]+):\s*(.*)', '"$1":"$2",' 304 | $jsons = $jsons + $line } 305 | $jsons =$jsons + '"blah" : "blah" }' 306 | #ConvertFrom-Json -InputObject $jsons 307 | $convert = ConvertFrom-Json -InputObject $jsons 308 | $Image = $convert.Image.Split("\")[-1] 309 | $TargetObject = $convert.TargetObject.Replace("\","\\") 310 | $Details = $convert.Details.Replace("\","\\") 311 | $ProcessId = $convert.ProcessId 312 | if ($pids -contains $ProcessId) { 313 | $results += "(id$ProcessId)-[:RegistryChange]->(registry$couter :RegistryEdit{name:'$Image',TargetObject:'$TargetObject',details:'$Details'}), " 314 | } 315 | 316 | $counter += 1 317 | } 318 | 319 | elseif($event.id -eq 7){ 320 | 321 | $ev = $event.Message -split "`r`n" 322 | $jsons="{ " 323 | foreach ($line in $ev) { 324 | $line=$line -replace "\\","\\" ` 325 | -replace "\{"," " ` 326 | -replace "\}"," " ` 327 | -replace '"','\"' ` 328 | -replace "`n"," " 329 | $line=$line -replace '(\s*[\w\s]+):\s*(.*)', '"$1":"$2",' 330 | $jsons = $jsons + $line } 331 | $jsons =$jsons + '"blah" : "blah" }' 332 | #ConvertFrom-Json -InputObject $jsons 333 | $convert = ConvertFrom-Json -InputObject $jsons 334 | $Image = $convert.Image.Split("\")[-1] 335 | $ImageLoaded = $convert.ImageLoaded.Replace("\","\\") 336 | $FileVersion = $convert.FileVersion 337 | $Description = $convert.Description 338 | $OriginalFileName = $convert.OriginalFileName 339 | $Signature = $convert.Signature 340 | $SignatureStatus = $convert.SignatureStatus 341 | $Signed = $convert.Signed 342 | $ProcessId = $convert.ProcessId 343 | if ($pids -contains $ProcessId) { 344 | $results += "(id$ProcessId)-[:ImageLoaded]->(imaege$couter :Image{name:'$Image',ImageLoaded:'$ImageLoaded',FileVersion:'$FileVersion',Description:'$Description',OriginalFileName:'$OriginalFileName',Signature:'$Signature',SignatureStatus:'$SignatureStatus',Signed:'$Signed'}), " 345 | } 346 | 347 | $counter += 1 348 | } 349 | elseif($event.id -eq 5){ 350 | 351 | $ev = $event.Message -split "`r`n" 352 | $jsons="{ " 353 | foreach ($line in $ev) { 354 | $line=$line -replace "\\","\\" ` 355 | -replace "\{"," " ` 356 | -replace "\}"," " ` 357 | -replace '"','\"' ` 358 | -replace "`n"," " 359 | $line=$line -replace '(\s*[\w\s]+):\s*(.*)', '"$1":"$2",' 360 | $jsons = $jsons + $line } 361 | $jsons =$jsons + '"blah" : "blah" }' 362 | #ConvertFrom-Json -InputObject $jsons 363 | $convert = ConvertFrom-Json -InputObject $jsons 364 | $ProcessId = $convert.ProcessId 365 | if ($pids -contains $ProcessId) { 366 | $results += "(id$ProcessId)-[:Terminated]->(temp$counter :Term), " 367 | } 368 | 369 | $counter += 1 370 | } 371 | 372 | else { 373 | write ("No idea") 374 | } 375 | } 376 | 377 | $results = $results.TrimEnd(",") 378 | $results += "return *" 379 | 380 | $results --------------------------------------------------------------------------------