Skip to content

Latest commit

 

History

History
116 lines (88 loc) · 6.04 KB

04-BrokenBranches.md

File metadata and controls

116 lines (88 loc) · 6.04 KB

❓ Hypothesis: attackers can be using new PE to compromise endpoints

📃 To-do:

  • Find rare processes
  • Find rare process associations
  • Find rare process trees

⏭️ Next: use the process trees to find anomalies outside of the XDR

First, let's check what the DeviceProcessEvent table looks like:

DeviceProcessEvents
| take 1

Let's do some stastistc based on process names, which in the table is actually called FileName.

DeviceProcessEvents
| summarize Total = count() by FileName
| order by Total asc

Now that we have a count, let's explore the process that showed only one time in our environment and run it against the FileProfile() function to get prevalance information.

🔗 FileProfile() documentation

DeviceProcessEvents
| summarize Total = count() by FileName, SHA256
| where Total == 1
| invoke FileProfile("SHA256")
| where GlobalPrevalence < 500

We can do the same thing for the parent processes:

DeviceProcessEvents
| summarize Total = count() by InitiatingProcessFileName, SHA256
| where Total == 1
| invoke FileProfile("SHA256")

After reading some threat intel report, it seems that it would be interresting to check what processes are started by processes such as powershell.exe and outlook.exe (yes that's totally arbitrary). Also, in the dataset we are exloring, there is an oddity... The process calc.exe seems to start processes...

DeviceProcessEvents
| where InitiatingProcessFileName in~ ("powershell.exe","outlook.exe","calc.exe")
| summarize count() by FileName, InitiatingProcessFileName

So we get this machine called broken4 which has some cool stuff (calc.exe is starting things...). Let's focus our search on it using the graph capabilities of KQL. We are going to use make-graph to turn our tabular data to a graph and then use graph-match to explore that data.

🔗 make-graph documentation
🔗 graph-match documentation

To turn our data to a graph, we will need a table with a list of nodes (processes) and a table with the list of edges (relation between the nodes, between the processes). Interrestingly, the "DeviceProcessEvents" has both. Within the same table we have the process and its relationship with another process: InitiatingProcessFileName is starting FileName, or using their identifiers, InitiatingProcessId is starting ProcessId. Let's build a graph for our device of interest:

let Processes = DeviceProcessEvents
| where DeviceName == "broken4";
Processes
| make-graph InitiatingProcessId --> ProcessId with Processes on ProcessId

This doesn't work as-is as the result is a graph and not a tabular output. So we'll explore it asking the paths between a parent process and its child processes all identified by their PID.

let Processes = DeviceProcessEvents
| where DeviceName == "broken4";
Processes
| make-graph InitiatingProcessId --> ProcessId with Processes on ProcessId
| graph-match (ParentProcess) -[Initiated*2..10]-> ()
  where isnotempty(ParentProcess.InitiatingProcessFileName)
  project ParentProcess = ParentProcess.InitiatingProcessFileName, ProcessTree = Initiated.FileName

This explores the paths between a parent process called ParentProcess to any process (). Path needs to be composed between 2 and 10 hops. It means that if a ParentProcess created a process which in its turn, didn't not create another process, this path will not be kept. And if a ParentProcess ended created a process tree depeer than 10 hops, we also stop looking at it (for perf reasons, we need to fix limits there...).

Looks good! Yet, there are a few problems with this... First of all, the PID are not unique. Windows can reuse them, so we might have found imaginary paths... We need to create a unique identifier for the processes. Normally we would use a hash_many() function but here we are going to do a simple concatenation with strcat() just for learning and readability.

let Processes = DeviceProcessEvents
| where DeviceName == "broken4"
| extend ProcessCompoundId = strcat(DeviceId,"-", FileName,"-", ProcessId,"-", ProcessCreationTime),
    InitiatingProccesCompoundId = strcat(DeviceId, "-", InitiatingProcessFileName,"-", InitiatingProcessId,"-", InitiatingProcessCreationTime);
Processes
| make-graph InitiatingProccesCompoundId --> ProcessCompoundId with Processes on ProcessCompoundId
| graph-match (ParentProcess) -[Initiated*2..10]-> ()
  where isnotempty(ParentProcess.InitiatingProcessFileName)
  project ParentProcessId = ParentProcess.InitiatingProccesCompoundId, ParentProcess = ParentProcess.InitiatingProcessFileName, ProcessTree = Initiated.FileName

Getting better, we have validated path. But really, explore the hash_many() function the next time to get more succinct.

🔗 hash_many() documentation

Now let's explore rare process trees. Let's keep the longest path per parent process:

let Processes = DeviceProcessEvents
| where DeviceName == "broken4"
| extend ProcessCompoundId = strcat(DeviceId,"-", FileName,"-", ProcessId,"-", ProcessCreationTime),
    InitiatingProccesCompoundId = strcat(DeviceId, "-", InitiatingProcessFileName,"-", InitiatingProcessId,"-", InitiatingProcessCreationTime);
Processes
| make-graph InitiatingProccesCompoundId --> ProcessCompoundId with Processes on ProcessCompoundId
| graph-match (ParentProcess) -[Initiated*2..10]-> ()
  where isnotempty(ParentProcess.InitiatingProcessFileName)
  project ParentProcessId = ParentProcess.InitiatingProccesCompoundId, ParentProcess = ParentProcess.InitiatingProcessFileName, ProcessTree = Initiated.FileName
| summarize Depth = arg_max(array_length(ProcessTree),*) by ParentProcessId

... TO BE CONTINUED