# T1106 - Native API

## SOC Recommendation
Investigate Native API activity in the context of Execution: confirm scope, affected host/identity, and whether it matches expected administrative behaviour before deciding this is benign.

## D3FEND Mappings
| D3FEND Technique | Relationship | Practical SOC Action |
|---|---|---|
| System Call Analysis | Detect | Monitor for System Call Analysis indicators relevant to this technique. |
| System Call Filtering | Isolate | Apply System Call Filtering to contain the blast radius once this technique is observed. |

## Investigation Steps
1. Review Defender for Endpoint / Defender XDR alerts and timeline for the affected host or identity.
2. Check Sentinel analytics rules and incidents correlated with this technique.
3. Review Entra ID sign-in and audit logs if the technique involves an identity or cloud resource.

## Evidence to Collect
- Host or resource affected
- Account or identity involved
- Timestamp of the activity
- Related process, file, or network artifact
- Any preceding or follow-on alerts

## Response Actions
| Action | Risk | Automation Safe | Approval Required |
|---|---|---|---|
| Contain the affected host or account | Medium | No | Yes |
| Collect and preserve evidence | Low | Yes | No |

## KQL
```kql
// Filter alerts from specific Microsoft security products with medium and high severity
SecurityAlert 
| where ProductName in ("Microsoft 365 Defender", "Azure Active Directory", "Microsoft Defender Advanced Threat Protection", "Microsoft Cloud App Security", "Azure Active Directory Identity Protection", "Microsoft Defender ATP")
| where AlertSeverity has_any ("Medium", "High")
// Parse JSON entities and extend AlertTimeGenerated
| extend Entities = parse_json(Entities), AlertTimeGenerated=TimeGenerated
// Extract and process IP entities
| mv-apply Entity = Entities on 
    ( 
    where Entity.Type == 'ip' 
    | extend EntityIp = tostring(Entity.Address) 
    ) 
// Extract and process account entities
| mv-apply Entity = Entities on 
    ( 
    where Entity.Type == 'account' 
    | extend AccountObjectId = tostring(Entity.AadUserId)
    )
// Filter out records with empty EntityIp
| where isnotempty(EntityIp)
// Summarize data and create sets of entities and system alert IDs
| summarize Entitys=make_set(Entity), SystemAlertIds=make_set(SystemAlertId)
    by 
    AlertName,
    ProductName,
    AlertSeverity,
    EntityIp,
    Tactics,
    Techniques,
    ProviderName,
    AlertTime= bin(AlertTimeGenerated, 1d),
    AccountObjectId
// Join with GCPAuditLogs for VM instance creation
| join kind=inner (
    GCPAuditLogs
    | where ServiceName == "compute.googleapis.com" and MethodName endswith "instances.insert"
    | extend
        GCPUserUPN= tostring(parse_json(AuthenticationInfo).principalEmail),
        GCPUserIp = tostring(parse_json(RequestMetadata).callerIp),
        GCPUserUA= tostring(parse_json(RequestMetadata).callerSuppliedUserAgent),
        VMStatus =  tostring(parse_json(Response).status),
        VMOperation=tostring(parse_json(Response).operationType),
        VMName= tostring(parse_json(Request).name),
        VMType = tostring(split(parse_json(Request).machineType, "/")[-1])
    | where GCPUserUPN !has "gserviceaccount.com"
    | where VMOperation == "insert" and isnotempty(GCPUserIp) and GCPUserIp != "private"
    | project
        GCPOperationTime=TimeGenerated,
        VMName,
        VMStatus,
        MethodName,
        GCPUserUPN,
        ProjectId,
        GCPUserIp,
        GCPUserUA,
        VMOperation,
        VMType
    )
    on $left.EntityIp == $right.GCPUserIp 
// Join with IdentityInfo to enrich user identity details
| join kind=inner (IdentityInfo 
    | distinct AccountObjectId, AccountUPN, JobTitle
    )
    on AccountObjectId 
// Calculate the time difference between the alert and VM creation for further analysis
| extend TimeDiff= datetime_diff('day', AlertTime, GCPOperationTime),Name = split(GCPUserUPN, "@")[0], UPNSuffix = split(GCPUserUPN, "@")[1]
```
```kql
let regexEmpire = tostring(toscalar(externaldata(cmdlets:string)[@"https://raw.githubusercontent.com/Azure/Azure-Sentinel/master/Sample%20Data/Feeds/EmpireCommandString.txt"] with (format="txt")));
(union isfuzzy=true
 (SecurityEvent
| where EventID == 4688
//consider filtering on filename if perf issues occur
//where FileName in~ ("powershell.exe","powershell_ise.exe","pwsh.exe")
| where not(ParentProcessName has_any ('gc_worker.exe', 'gc_service.exe'))
| where CommandLine has "-encodedCommand"
| parse kind=regex flags=i CommandLine with * "-EncodedCommand " encodedCommand
| extend encodedCommand = iff(encodedCommand has " ", tostring(split(encodedCommand, " ")[0]), encodedCommand)
// Note: currently the base64_decode_tostring function is limited to supporting UTF8
| extend decodedCommand = translate('\0','', base64_decode_tostring(substring(encodedCommand, 0, strlen(encodedCommand) -  (strlen(encodedCommand) %8)))), encodedCommand, CommandLine , strlen(encodedCommand)
| extend EfectiveCommand = iff(isnotempty(encodedCommand), decodedCommand, CommandLine)
| where EfectiveCommand matches regex regexEmpire
| project timestamp = TimeGenerated, Computer, SubjectUserName, SubjectDomainName, FileName = Process, EfectiveCommand, decodedCommand, encodedCommand, CommandLine, ParentProcessName
| extend HostName = split(Computer, '.', 0)[0], DnsDomain = strcat_array(array_slice(split(Computer, '.'), 1, -1), '.')
),
(WindowsEvent
| where EventID == 4688
| where EventData has_any ("-encodedCommand", "powershell.exe","powershell_ise.exe","pwsh.exe")
| where not(EventData has_any ('gc_worker.exe', 'gc_service.exe'))
//consider filtering on filename if perf issues occur
//extend NewProcessName = tostring(EventData.NewProcessName)
//extend Process=tostring(split(NewProcessName, '\\')[-1])
//FileName = Process
//where FileName in~ ("powershell.exe","powershell_ise.exe","pwsh.exe")
| extend ParentProcessName = tostring(EventData.ParentProcessName)
| where not(ParentProcessName has_any ('gc_worker.exe', 'gc_service.exe'))
| extend CommandLine = tostring(EventData.CommandLine)
| where CommandLine has "-encodedCommand"
| parse kind=regex flags=i CommandLine with * "-EncodedCommand " encodedCommand
| extend encodedCommand = iff(encodedCommand has " ", tostring(split(encodedCommand, " ")[0]), encodedCommand)
// Note: currently the base64_decode_tostring function is limited to supporting UTF8
| extend decodedCommand = translate('\0','', base64_decode_tostring(substring(encodedCommand, 0, strlen(encodedCommand) -  (strlen(encodedCommand) %8)))), encodedCommand, CommandLine , strlen(encodedCommand)
| extend EfectiveCommand = iff(isnotempty(encodedCommand), decodedCommand, CommandLine)
| where EfectiveCommand matches regex regexEmpire
| extend SubjectUserName = tostring(EventData.SubjectUserName)
| extend SubjectDomainName = tostring(EventData.SubjectDomainName)
| extend NewProcessName = tostring(EventData.NewProcessName)
| extend Process=tostring(split(NewProcessName, '\\')[-1])
| project timestamp = TimeGenerated, Computer, SubjectUserName, SubjectDomainName, FileName = Process, EfectiveCommand, decodedCommand, encodedCommand, CommandLine, ParentProcessName
| extend HostName = split(Computer, '.', 0)[0], DnsDomain = strcat_array(array_slice(split(Computer, '.'), 1, -1), '.')
))
```
```kql
let query_frequency = 1h;
let query_lookback = 24h;
// AppID of the multi-tenant Dynamics 365 Example Client Application
let well_known_app_id = "51f81489-12ee-4a9e-aaae-a2591f45987d";
let environment_count_threshold = 10;
SigninLogs
| where TimeGenerated >= ago(query_lookback)
// Comment out the line below to monitor activity from all Azure AD apps
| where AppId == well_known_app_id
| where ResourceIdentity == '00000007-0000-0000-c000-000000000000'
| summarize FirstSeen = min(TimeGenerated) by AppId, UserPrincipalName, IPAddress, AppDisplayName
| join kind=inner (
    DataverseActivity
    | where TimeGenerated >= ago(query_frequency)
    | where Message == "UserSignIn")
    on $left.UserPrincipalName == $right.UserId, $left.IPAddress == $right.ClientIp
| where TimeGenerated between (FirstSeen .. (FirstSeen + 2h))
| summarize InstanceCount = dcount(InstanceUrl, 4), FirstSeen = min(FirstSeen) by UserId, ClientIp, InstanceUrl, AppDisplayName, AppId
| where InstanceCount > environment_count_threshold
| extend
    CloudAppId = int(32780),
    AccountName = tostring(split(UserId, '@')[0]),
    UPNSuffix = tostring(split(UserId, '@')[1])
| project
    FirstSeen,
    UserId,
    ClientIp,
    AppDisplayName,
    AppId,
    InstanceUrl,
    CloudAppId,
    AccountName,
    UPNSuffix
```

## Escalation Criteria
- Native API activity observed on a privileged account or critical system.
- Activity follows or precedes other suspicious behaviour in the same investigation.
- Automated triage cannot confidently rule out malicious intent.

## False Positive Considerations
- Legitimate administrative or maintenance activity matching this pattern.
- Approved security testing or red team exercise.
- Known benign software producing similar telemetry.

Generated by SOC Response Atlas by Basyrix.
