Home

Awesome

DFIR-O365RC

Docker Image CI

Table of contents:

  1. Module description

  2. Installation and prerequisites

    1. Using Docker
    2. Manual Installation
  3. Managing the DFIR-O365RC application

    1. Creating the application
    2. Updating the application
    3. Removing the application
  4. Permissions and license requirements

  5. Functions included in the module

  6. Files generated

DFIR-O365RC was presented at SSTIC 2021 (Symposium sur la sécurité des technologies de l'information et des communications). Slides and a recording of the presentation, in French, are available here.

⚠️ On March 31, 2024, Microsoft deprecated the authentication method we used for DFIR-O365RC. This led to the release of the version 2.0.0 in August 2024, with breaking changes regarding authentication and a global refactoring of the code. ⚠️

Module description

The DFIR-O365RC PowerShell module is a set of functions that allow a forensic analyst to collect logs relevant for Microsoft 365 compromises and conduct Entra ID investigations.

The logs are generated in JSON format and retrieved from two main data sources:

Those two data sources can be queried from different endpoints:

Data source / EndpointRetentionPerformanceScope
Unified Audit Log / Exchange Online PowerShell90 daysPoorAll Microsoft 365 logs (Entra included)
Unified Audit Log / Purview180 daysGoodAll Microsoft 365 logs (Entra included)
Unified Audit Log / Office 365 Management API *7 daysGoodAll Microsoft 365 logs (Entra included)
Microsoft Entra logs / Microsoft Graph PowerShell30 daysGoodEntra sign-ins and audit logs only
Microsoft Entra logs / Microsoft Graph REST API30 daysGoodEntra sign-ins and audit logs only

* The Office 365 Management API is intended to analyze data in real time with a SIEM. DFIR-O365RC is a forensic tool, its aim is not to monitor a Microsoft 365 environment in real time.

DFIR-O365RC will fetch data from:

If you are investigating Microsoft 365 malicious activity, the Search-O365 (from Exchange Online PowerShell) will also fetch the Mailbox Audit Log, although the Search-MailboxAuditLog cmdlet is being deprecated.

If you are investigating other Azure resources, with DFIR-O365RC:

Installation and prerequisites

Using Docker

This is the recommended way of using DFIR-O365RC

Clone the repository and use docker compose (or the legacy docker-compose) to build the image, run the container and mount a volume (in the output/ folder):

sudo docker compose run dfir-o365rc
# using legacy Compose V1
sudo docker-compose run dfir-o365rc

DFIR-O365RC is ready to use:

PowerShell 7.4.2
DFIR-O365RC: PowerShell module for Microsoft 365 and Entra ID log collection
https://github.com/ANSSI-FR/DFIR-O365RC
PS /mnt/host/output>

Manual Installation

Clone the DFIR-O365RC repository. The module works on PowerShell Desktop and PowerShell Core.

Please note that the Connect-ExchangeOnline cmdlet requires Microsoft .NET Framework 4.7.2 or later.

DFIR-O365RC uses Boe Prox's PoshRSJob module as well as a lot of Microsoft modules to interact with the required SDKs.

Install them by running:

Install-Module Az.Accounts -RequiredVersion 3.0.2
Install-Module Az.Monitor -RequiredVersion 5.2.1
Install-Module Az.Resources -RequiredVersion 7.2.0
Install-Module ExchangeOnlineManagement -RequiredVersion 3.5.1
Install-Module Microsoft.Graph.Authentication -RequiredVersion 2.20.0
Install-Module Microsoft.Graph.Applications -RequiredVersion 2.20.0
Install-Module Microsoft.Graph.Beta.Reports -RequiredVersion 2.20.0
Install-Module Microsoft.Graph.Beta.Security -RequiredVersion 2.20.0
Install-Module Microsoft.Graph.Identity.DirectoryManagement -RequiredVersion 2.20.0
Install-Module PoshRSJob -RequiredVersion 1.7.4.4

Once the modules are installed, launch a PowerShell prompt and locate your Powershell modules path:

PS> $env:PSModulePath

Copy the DFIR-O365RC directory in one of your modules path, for example:

Restart the PowerShell prompt and import the DFIR-O365RC module:

PS> Import-Module DFIR-O365RC

Managing the DFIR-O365RC application

Creating the application

Once the module is imported, you will need to create an Entra application, which will handle the log collection process for you.

To do so:

  1. Create a self-signed certificate and get the base64-encoded public part:

    On Linux, using PowerShell Core or the Docker container:

    openssl req -new -x509 -newkey rsa:2048 -sha256 -days 365 -nodes -out exampleDFIRO365RC.crt -keyout exampleDFIRO365RC.key -batch
    openssl pkcs12 -inkey exampleDFIRO365RC.key -in exampleDFIRO365RC.crt -export -out exampleDFIRO365RC.pfx # Enter a password for the certificate
    openssl x509 -in exampleDFIRO365RC.crt -outform DER -out - | base64 | tr -d "\n"
    

    On Windows, using PowerShell:

    $certificate = New-SelfSignedCertificate -Subject "CN=exampleDFIRO365RC" -KeySpec KeyExchange -NotBefore (Get-Date) -NotAfter (Get-Date).AddDays(365)
    $certificatePassword = Read-Host -MaskInput "Please enter a password for the certificate"
    $certificateSecurePassword = ConvertTo-SecureString -String $certificatePassword -AsPlainText -Force
    Export-PfxCertificate -Cert $certificate -FilePath exampleDFIRO365RC.pfx -Password $certificateSecurePassword
    Write-Host ([System.Convert]::ToBase64String($certificate.GetRawCertData()))
    
  2. Use the New-Application cmdlet from the DFIR-O365RC module:

    $certificateb64="<base64-encoded public part from step 1>"
    New-Application -certificateb64 $certificateb64
    

    Optionally, if you would like to be able to gather logs in the subscriptions of the tenant (not needed if you do not plan to use Get-AzRMActivityLogs):

    New-Application -certificateb64 $certificateb64 -subscriptions
    

    Optionally, if you would like to be able to gather logs in the Azure DevOps organizations of the tenant (this can take a long time and is not needed if you do not plan to use Get-AzDevOpsActivityLogs):

    New-Application -certificateb64 $certificateb64 -organizations
    

    To create the application, you will need to log in to Azure several times, using a highly privileged account.

    One the application is created, you will get an output similar to:

    Done creating the application with the required permissions
    Please use the following identifiers: 
    WARNING: AppID: xxxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
    WARNING: Tenant: example.onmicrosoft.com
    

Updating the application

Once the application is created, you can still, using the Update-Application cmdlet from the module, update its credentials and permissions:

Removing the application

Once you are done with the log collection you can delete the application using the Remove-Application cmdlet from the module.

To remove the application, you will need to log in to Azure several times, using a highly privileged account.

If the application was able to gather logs of subscriptions or Azure DevOps organizations, you will need to add the -organizations and/or -subscriptions switches.

Permissions and license requirements

⚠️

Starting with version 2.0.0, the tool is now running in the context of a Service Principal with App-only access / Application permissions.

To use version 2.0.0 and up, you will need to create an application.

Once the application is created, the script will run using the application's credentials and permissions.

⚠️

The application will be created with the least possible required permission set:

Optionally (if using the -subscriptions switch):

Optionally (if using the -organizations switch):

In order to retrieve Microsoft Entra logs with the Microsoft Graph API you need at least one user with a Microsoft Entra ID P1 license. This license can be purchased for a single user or can be included in some license plans such as the Microsoft 365 Business Premium plan.

Functions included in the module

The module has 9 functions:

FunctionData SourceRetentionPerformanceCompletenessDetails
Get-O365FullUnified Audit Log90 days / 180 days*PoorAll Unified Audit LogBy default, retrieve the whole Unified Audit Log. This should only be used on a small tenant or a short period of time.<br />You can also use this cmdlet to gather events for some specific record types.
Get-O365LightUnified Audit Log90 days / 180 days*GoodA subset of Unified Audit Log onlyOnly a subset of operations, which are considered of interest, are retrieved.
Get-O365DefenderUnified Audit Log90 days / 180 days*GoodA subset of Unified Audit Log onlyRetrieves Microsoft Defender for Microsoft 365 related events. Requires at least one Office 365 E5 license or a license plan which includes Microsoft Defender for Office 365.
Get-AADLogsMicrosoft Entra Logs30 daysGoodAll Microsoft Entra LogsGet tenant information and all Microsoft Entra logs: sign-ins logs and audit logs.
Get-AADAppsMicrosoft Entra Logs + Entra ID30 daysGoodA subset of Microsoft Entra Logs onlyGet Microsoft Entra audit logs related to Entra applications and service principals only.<br />The logs are enriched with application or service principal object information.
Get-AADDevicesMicrosoft Entra Logs + Entra ID30 daysGoodA subset of Microsoft Entra Logs onlyGet Microsoft Entra audit logs related to Entra ID joined or registered devices only.<br />The logs are enriched with device object information.
Search-O365Unified Audit Log / Mailbox Audit Log**90 days / 180 days*PoorA subset of Unified Audit Log onlySearch for activity related to specific users, IP addresses or free texts.
Get-AzRMActivityLogsAzure Monitor Activity log90 daysGoodAll Azure Monitor Activity logGet all Azure Monitor Activity log for a selected subset of subscriptions.
Get-AzDevOpsActivityLogsAzure DevOps audit log90 daysGoodAll Azure DevOps audit logGet all Azure DevOps audit log for a selected subset of Azure DevOps organizations.

* You can get 180 days of retention using Purview, compared to the default 90 days of retention using Exchange Online.

** When searching for users, the Search-O365 cmdlet will also search in the Mailbox Audit Log.

Each function as a comment-based help which you can invoke with the Get-Help cmdlet.

# Display comment-based help
PS> Get-Help Get-O365Full
# Display comment-based help with examples
PS> Get-Help Get-O365Full -Examples

Each function takes as a parameter:

Examples:

For readability, we will assume that:

$appId = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
$tenant = "example.onmicrosoft.com"
$certificatePath = "./example.pfx"

On a real case, those parameters are gathered when creating the application.

In order to retrieve Microsoft Entra Logs from the past 30 days as well as general information on the tenant:

$endDate = Get-Date
$startDate = $endDate.AddDays(-30)
Get-AADLogs -startDate $startDate -endDate $endDate -appId $appId -tenant $tenant -certificatePath $certificatePath

Get Microsoft Entra audit logs related to Entra applications and service principals from the past 30 days:

$endDate = Get-Date
$startDate = $endDate.AddDays(-30)
Get-AADApps -startDate $startDate -endDate $endDate -appId $appId -tenant $tenant -certificatePath $certificatePath

Get Microsoft Entra audit logs related to Entra ID joined or registered devices from the past 30 days:

$endDate = Get-Date
$startDate = $endDate.AddDays(-30)
Get-AADDevices -startDate $startDate -endDate $endDate -appId $appId -tenant $tenant -certificatePath $certificatePath

Retrieve Unified Audit log events considered of interest from the past 30 days, except those related to Entra ID, which were already retrieved by the first command:

$endDate = Get-Date
$startDate = $endDate.AddDays(-30)
Get-O365Light -startDate $startDate -endDate $endDate -appId $appId -tenant $tenant -certificatePath $certificatePath -operationsSet "allButAzureAD"

Retrieve Unified Audit log events considered of interest in a time window between -90 days and -30 days from now:

$endDate = Get-Date.AddDays(-30)
$startDate = Get-Date.AddDays(-90)
Get-O365Light -startDate $startDate -endDate $endDate -appId $appId -tenant $tenant -certificatePath $certificatePath

If mailbox audit is enabled you can also retrieve MailboxLogin operations using the dedicated switch:

Beware of a global limit of 50.000 events per search

Get-O365Light -startDate $startDate -endDate $endDate -appId $appId -tenant $tenant -certificatePath $certificatePath -mailboxLogin

If there are users with Office 365 E5 licenses or if there is a Microsoft Defender for Office 365 Plan in the tenant you can retrieve Microsoft Defender related logs from the past 90 days:

$endDate = Get-Date
$startDate = $endDate.AddDays(-90)
Get-O365Defender -startDate $startDate -endDate $endDate -appId $appId -tenant $tenant -certificatePath $certificatePath

To retrieve all Unified Audit Log events between Christmas Eve 2020 and Boxing day 2020:

Beware that performance using that cmdlet is poor

$endDate = Get-Date "12/26/2020"
$startdate = Get-Date "12/24/2020"
Get-O365Full -startDate $startDate -endDate $endDate -appId $appId -tenant $tenant -certificatePath $certificatePath

You can use the search function to look for IP addresses, activity related to specific users or perform a freetext search in the Unified Audit Log:

$endDate = Get-Date
$startDate = $endDate.AddDays(-90)

# Retrieve events which contains the "Python" or "Python3" free text
Search-O365 -startDate $startDate -endDate $endDate -appId $appId -tenant $tenant -certificatePath $certificatePath -freeTexts "Python","Python3"

# Retrieve events related to the IP adresses 8.8.8.8 and 4.4.4.4.
Search-O365 -startDate $startDate -endDate $endDate -appId $appId -tenant $tenant -certificatePath $certificatePath -IPAddresses "8.8.8.8","4.4.4.4"

# Retrieve events related to users user1@example.onmicrosoft.com and user2@example.onmicrosoft.com
Search-O365 -startDate $startDate -endDate $endDate -appId $appId -tenant $tenant -certificatePath $certificatePath -userIds "user1@example.onmicrosoft.com","user2@example.onmicrosoft.com"

When searching for specific users, Search-O365 will also search in the Mailbox Audit Log. That's because, depending on the user's license level and settings, some of the mailbox logs might not be present in the Unified Audit Log.

To retrieve all Azure Resource Manager activity logs from the subscriptions the application has access to:

$endDate = Get-Date
$startDate = $endDate.AddDays(-90)
Get-AzRMActivityLogs -startDate $startDate -endDate $endDate -appId $appId -tenant $tenant -certificatePath $certificatePath

To retrieve all Azure DevOps activity logs from the organizations the application has access to:

$endDate = Get-Date
$startDate = $endDate.AddDays(-90)
Get-AzDevOpsActivityLogs -startDate $startDate -endDate $endDate -appId $appId -tenant $tenant -certificatePath $certificatePath

Files generated

All files generated are in JSON format.

Launching several cmdlet which uses Purview and will write to the same output file can result in an invalid JSON because of a "naive" concatenation

Launching the various functions will generate a directory structure similar to this one:

output
│   Get-AADApps.log
│   Get-AADDevices.log
│   Get-AADLogs.log
│   Get-AzDevOpsActivityLogs.log
│   Get-AzRMActivityLogs.log
│   Get-O365Defender.log
│   Get-O365Full.log
│   Get-O365Light.log
│   Search-O365.log
│
├───azure_ad_apps
│       AADApps_example.onmicrosoft.com.json
│       AADApps_example.onmicrosoft.com_deleted_applications_raw.json
│       AADApps_example.onmicrosoft.com_existing_applications_raw.json
│       AADApps_example.onmicrosoft.com_service_principals_raw.json
│
├───azure_ad_audit
│       AADAuditLog_example.onmicrosoft.com_YYYY-MM-DD.json
│       [...]
│
├───azure_ad_devices
│       AADDevices_example.onmicrosoft.com.json
│       AADDevices_example.onmicrosoft.com_devices_raw.json
│
├───azure_ad_signin
│   ├───YYYY-MM-DD
│   │       AADSigninLog_example.onmicrosoft.com_YYYY-MM-DD_HH-00-00.json
│   │       [...]
│   │
│   ├───[...]
│
├───azure_ad_tenant
│       AADTenant_example.onmicrosoft.com.json
│
├───azure_DevOps_activity
│   ├───YYYY-MM-DD
│   │       AzDevOps_example.onmicrosoft.com_%AzureDevOpsOrg%_YYYY-MM-DD_HH-00-00.json
│   │       [...]
│   │
│   ├───[...]
│
├───azure_DevOps_orgs
│       AzdevopsOrgs_example.onmicrosoft.com.json
├───azure_rm_activity
│   ├───YYYY-MM-DD
│   │       AzRM_example.onmicrosoft.com_%SubscriptionID%_YYYY-MM-DD_HH-00-00.json
│   │       [...]
│   │
│   ├───[...]
│
├───azure_rm_subscriptions
│       AzRMsubscriptions_example.onmicrosoft.com.json
│
├───Exchange_mailbox_audit_logs
│   └───YYYY-MM-DD
│   │       MailboxAuditLog_example.onmicrosoft.com_YYYY-MM-DD_HH-00-00_UserIds_YYYY-MM-DD-HH-MM-SS_%UserID%.json
│   │       [...]
│   │
│   ├───[...]
│
├───O365_unified_audit_logs
│   ├───YYYY-MM-DD
│   │       UnifiedAuditLog_example.onmicrosoft.com_YYYY-MM-DD_HH-00-00.json
│   │       UnifiedAuditLog_example.onmicrosoft.com_YYYY-MM-DD_HH-00-00_%RecordType%.json
│   │       UnifiedAuditLog_example.onmicrosoft.com_YYYY-MM-DD_HH-00-00_UserIds_YYYY-MM-DD-HH-MM-SS.json
│   │       UnifiedAuditLog_example.onmicrosoft.com_YYYY-MM-DD_HH-00-00_IPAddresses_YYYY-MM-DD-HH-MM-SS.json
│   │       UnifiedAuditLog_example.onmicrosoft.com_YYYY-MM-DD_HH-00-00_FreeText_YYYY-MM-DD-HH-MM-SS_%i.json
│   │       [...]
│   │
│   ├───[...]
│
└───O365_unified_audit_logs_purview
│   ├───YYYY-MM-DD
│   │       UnifiedAuditLogPurview_example.onmicrosoft.com_YYYY-MM-DD_HH-00-00.json
│   │       UnifiedAuditLogPurview_example.onmicrosoft.com_YYYY-MM-DD_HH-00-00_%RecordType%.json
│   │       UnifiedAuditLogPurview_example.onmicrosoft.com_YYYY-MM-DD_HH-00-00_UserIds_YYYY-MM-DD-HH-MM-SS.json
│   │       UnifiedAuditLogPurview_example.onmicrosoft.com_YYYY-MM-DD_HH-00-00_IPAddresses_YYYY-MM-DD-HH-MM-SS.json
│   │       UnifiedAuditLogPurview_example.onmicrosoft.com_YYYY-MM-DD_HH-00-00_FreeText_YYYY-MM-DD-HH-MM-SS_%i.json
│   │       [...]
│   │
│   ├───[...]