How to get Antivirus-related Data from Microsoft Defender for Endpoint using Intune and Graph API

Hello everyone! In this episode, I would like to tell you how I tried to get automatically antivirus-related data (current status, engine and signature version, last full scan date) from Microsoft Defender for Endpoint using Microsoft Intune and the Graph API.

Why is this necessary?

You might assume that if the Defender for Endpoint agent is installed on the host, everything should be fine automatically. But in fact, the antivirus engine and signature versions may be outdated, real-time protection may be disabled. And so all this needs to be monitored.

Grapf API

This will be the third episode about Microsoft Enterprise Security APIs. The first was about Defender and Defender API, the second was about Intune and the Intune API. And today I’m going to talk about the Grapf API, which should probably replace all the other APIs and should be more logical and easier. Although in my opinion it is even strangier and poorly documented. I didn’t like it.

App Registration

To get started with the Graph API, you need to register an Azure app. I described this in detail in the Defender for Endpoint episode. Here I am only listing the Microsoft Graph permissions I used:

  • Device.Read.All
  • DeviceManagementConfiguration.Read.All
  • DeviceManagementManagedDevices.Read.All

Access Token

To get a token, execute the following code:

import json
import requests

def get_token():
    body = {
        "resource": "https://graph.microsoft.com",
        "client_id": azure_appId,
        "client_secret": azure_appSecret,
        "grant_type": "client_credentials"
    }
    response = requests.post("https://login.microsoft.com/" + azure_tenantId + "/oauth2/token", data=body)
    return response.json()["access_token"]

And then the question is how to use this token.

What didn’t work

Now I will list what I tried and what did not work:

What kind of worked

This kind of worked. You can get all device IDs with https://graph.microsoft.com/beta/deviceManagement/managedDevices, and then get windowsProtectionState results with separate queries https://graph.microsoft.com/beta/deviceManagement/managedDevices/{DeviceID}/windowsProtectionState. This works, but for a large number of hosts, making a large number of requests is crazy.

What worked

Finally export of DefenderAgents report worked. First of all, you need to configure the report parameters and order it. You will receive a unique report ID.

import json
import requests
import zipfile
import csv


auth_token = get_token()

headers = {
'Content-Type': 'application/json',
'Accept': 'application/json',
'Authorization': "Bearer " + auth_token
}

body = {
"reportName": "DefenderAgents",
"select": [
    "DeviceId",
    "DeviceName",
    "DeviceState",
    "PendingFullScan",
    "PendingReboot",
    "PendingManualSteps",
    "PendingOfflineScan",
    "CriticalFailure",
    "MalwareProtectionEnabled",
    "RealTimeProtectionEnabled",
    "NetworkInspectionSystemEnabled",
    "SignatureUpdateOverdue",
    "QuickScanOverdue",
    "FullScanOverdue",
    "RebootRequired",
    "FullScanRequired",
    "EngineVersion",
    "SignatureVersion",
    "AntiMalwareVersion",
    "LastQuickScanDateTime",
    "LastFullScanDateTime",
    "LastQuickScanSignatureVersion",
    "LastFullScanSignatureVersion",
    "LastReportedDateTime",
    "UPN",
    "UserEmail",
    "UserName"
]
}

response = requests.post("https://graph.microsoft.com/beta/deviceManagement/reports/exportJobs", data=json.dumps(body), headers=headers)
report_id = response.json()['id']
print(json.dumps(response.json(), indent=4))

Then you wait and check the status of the report using the ID. Until it is “completed”.

status = ""
while status != "completed":
    # report_id = "DefenderAgents_0dc67ffe-6fa4-4fcf-bd1f-2e4c94ca2612"
    url = "https://graph.microsoft.com/beta/deviceManagement/reports/exportJobs('" + report_id + "')"
    response = requests.get(url, headers=headers)
    status = response.json()['status']
    print("Waiting...")

Then you download the report as a zipped csv and save it.

r = requests.get(response.json()['url'])
f = open('data/defender_agents.zip', 'wb')
f.write(r.content)

It remains only to turn the contents of the file into a convenient dict. You can do it like this:

myzip = zipfile.ZipFile('data/defender_agents.zip')
myfile = myzip.open(myzip.namelist()[0])
lines = str(myfile.read()).split("\\r\\n")
rows = list()
for row in csv.reader(lines):
rows.append(row)
result_dict = dict()
for row_mumber in range(1, len(rows)):
if len(rows[row_mumber]) == len(rows[0]):
    line_dict = dict()
    for param_number in range(0, len(rows[0])):
        line_dict[rows[0][param_number]] = rows[row_mumber][param_number]
    result_dict[line_dict['DeviceName'].lower()] = line_dict
print(result_dict)

Leave a Reply

Your email address will not be published.

This site uses Akismet to reduce spam. Learn how your comment data is processed.