skip to content

Process Ancestry

A signed script host with an encoded command line is either the packaging team or an intruder, and only the parent chain tells you which. The whiteboard exercise almost every SOC interview includes.

on this pageshow

explore

questions

4

An alert fires on powershell.exe with an encoded command line — what does its parent chain add to the verdict?

level: juniorimportance: must knowfreq 72%

answer

  1. the child binary is almost always signed
  2. read upward until the root explains it
  3. encoding is a packaging choice, not intent
  4. join parent to child on the GUID
  5. document app spawning a shell is the tell

basics

~20 s

The parent chain says who asked for it. Encoded PowerShell under a document application means a user opened something that executed code; the same command under a management agent's service means scheduled automation. The lineage, not the child, carries the verdict.

solid answer

~50 s

The child process on its own is almost content-free: `powershell.exe -nop -w hidden -enc <base64>` is run every day by installers, management agents and attackers alike, and the encoding proves nothing about intent. What separates them is the ancestry. If I walk up from the alerted process and find `explorer.exe` -> `WINWORD.EXE` -> `cmd.exe` -> `powershell.exe` under an interactive user at medium integrity, a document the user opened just executed code — that is user execution followed by a command interpreter (`T1204.002` into `T1059.001`), and it is suspicious before I have decoded a single byte. If instead I find `services.exe` -> a management agent -> the same PowerShell running as SYSTEM in session 0, I am looking at automation. I rebuild the chain by joining each Sysmon Event ID 1 record to its parent's own record on `ParentProcessGuid`, decode the argument to see what was actually asked for, and only then decide whether to escalate.

code

json · 14 lines
json
[
  { "EventID": 1, "Image": "C:\\Program Files\\Microsoft Office\\root\\Office16\\WINWORD.EXE",
    "ProcessGuid": "{9a1f...-1a2b}", "ParentImage": "C:\\Windows\\explorer.exe",
    "User": "CORP\\j.reed", "IntegrityLevel": "Medium", "...": "..." },

  { "EventID": 1, "Image": "C:\\Windows\\System32\\cmd.exe",
    "CommandLine": "cmd.exe /c powershell -nop -w hidden -enc SQBFAFgA...",
    "ParentImage": "...\\WINWORD.EXE", "ParentProcessGuid": "{9a1f...-1a2b}",
    "User": "CORP\\j.reed", "...": "..." },

  { "EventID": 1, "Image": "C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe",
    "CommandLine": "powershell -nop -w hidden -enc SQBFAFgA...",
    "ParentImage": "C:\\Windows\\System32\\cmd.exe", "User": "CORP\\j.reed", "...": "..." }
]

go deeper

for a junior

Be ready to read a four-record chain aloud, oldest first, and name the root. Know that the alerted binary is usually legitimate and that encoding is packaging, not intent.

for a middle

Explain how you actually rebuild the chain from records — joining each process to its parent on a unique process GUID rather than on a reusable process id — and what stays constant down a chain that belongs to one session.

for a senior

Show that you state conclusions in the right direction: the chain proves what was created, not what it did, and a clean-looking lineage is not clearance because hijacked automation inherits its host's ancestry.

for a principal

Own the reason ancestry is worth collecting at all: a defensible verdict rests on causal context rather than on binary reputation, and that is the argument for paying to keep parent linkage in telemetry across the fleet.

## What process ancestry means Every process creation is recorded with a pointer back to the process that created it. Sysmon Event ID 1 (process create) carries `Image` and `CommandLine` for the new process plus `ParentImage`, `ParentProcessId` and `ParentProcessGuid` for its creator; Windows Security 4688 ("a new process has been created") carries the new process name and the creator process name and id. One record is therefore one link. **Ancestry** is what you get when you follow those links upward: child, parent, grandparent, great-grandparent, until you reach a root that explains the whole thing — `explorer.exe` for something a person launched, `services.exe` for something a service started, `svchost.exe` hosting the Task Scheduler for something scheduled, `WmiPrvSE.exe` for something invoked over WMI. Widen by joining on the GUID, not the number. A process id is a small integer that Windows reuses freely, so joining a child to a parent by PID across a long time window can attach you to an unrelated process that happened to inherit that number later. `ParentProcessGuid` identifies exactly one process on exactly one host, so the join is safe. ## Why the chain and not the child The alerted process is usually a shared, signed, legitimate binary. `powershell.exe`, `cmd.exe`, `wscript.exe`, `rundll32.exe` and `mshta.exe` are all Microsoft-signed components sitting in `System32`, and all of them run thousands of times a day for entirely ordinary reasons. So does base64. `-EncodedCommand` (`-enc`) exists because passing a script with quotes, newlines and semicolons through a command line is painful; the argument is simply the script text encoded as UTF-16LE base64. Software distribution tools, installer wrappers and patch agents use it constantly. **Decoding it tells you what was asked for; it never tells you who asked, and "it was encoded" is not a finding.** The chain is where the intent lives, because it answers a question the child cannot: what caused this to run? A user's document application has no business creating a command interpreter. Neither does a browser's renderer, a PDF reader, an email client or a spreadsheet. When one of them is the grandparent, the most economical explanation is that content the user opened carried code — and that is exactly the shape that an initial-access technique leaves behind. ## Reading the example chain Take the three records in the code example. Read them oldest first, from the root down: 1. `WINWORD.EXE` is created by `explorer.exe` as `CORP\j.reed` at medium integrity. Normal — a person double-clicked a document. 2. `WINWORD.EXE` creates `cmd.exe` with `/c powershell -nop -w hidden -enc ...`. Abnormal. Word does not spawn shells during ordinary editing. 3. `cmd.exe` creates `powershell.exe` with the same encoded argument, still as `CORP\j.reed`. The interesting record is the second one, not the third. The alert fired on the PowerShell, but the verdict comes from its grandparent. Note also what stays constant down the chain — the same user, the same logon session, the same integrity level — because that is what lets you say the whole chain belongs to one person's session rather than to two unrelated things that happened to overlap in time. ## What the chain does and does not prove Be precise about direction. A process-creation chain proves that these processes were **created**, in this order, by these creators. It does not prove that any of them did anything: the PowerShell may have failed instantly, the decoded script may have errored, the document macro may have been a badly written internal tool. Equally, the ancestry looking normal is not clearance — an adversary who obtains execution inside a legitimate parent inherits that parent's lineage, and the chain will look exactly like the automation it hijacked. ## What to do with it Decode the argument and read it. Note the root of the chain and the account. If the root is a user's application, the next steps concern what the user opened and where it came from; if the root is a service, the next question is which service and whether anyone owns it. Then write the chain into the case as a short timeline — image, parent, command line, time — so that whoever reads it after you inherits the lineage rather than reconstructing it.

  • You decode the base64 argument and it is a short script that downloads and runs a file. Does that change your verdict?
    It sharpens it but does not create it. The decoded content tells you what was requested — a fetch and execute — which is worth escalating from a document-application parent, and completely routine from a software-distribution agent. Intent still comes from the pairing: this content under this ancestry. Record the decoded text in the case either way, because it is the artefact the next analyst will want and re-decoding it later may not be possible.
  • The chain looks identical except the root is a browser rather than a word processor. Does that matter?
    Not to the reasoning. A browser, a PDF reader, a mail client and a spreadsheet are all content-rendering applications with no ordinary reason to create a command interpreter, so any of them appearing as the grandparent of a shell is the same finding. What changes is the next step: for a browser you go looking at what was being visited or downloaded, rather than at an attachment.
  • Why walk up the chain rather than just look at the alerted process's own parent?
    Because the immediate parent is often uninformative. `cmd.exe` spawning `powershell.exe` says nothing at all; every verdict in that example lives one generation further up, in whatever created the `cmd.exe`. Adversaries also chain through several ordinary interpreters. Keep climbing until you reach a root that actually explains why anything ran — a person's application, a service, or a scheduler.

A single process is a sentence quoted out of context. The ancestry is the paragraph around it: the same words read very differently after "the user opened an attachment" than after "the patch agent woke up".

saying these in an interview costs you the question

  • Calls the encoded argument proof of malicious intent
  • Judges the alert on the child binary alone
  • Says signed Microsoft binaries in System32 are safe
  • Joins parent to child by process id instead of GUID
  • Treats a normal-looking ancestry as an all-clear

context

open as a page

The same encoded PowerShell runs under WINWORD.EXE on one host and under an RMM agent on 4,000 — how does ancestry decide each verdict?

level: middleimportance: should knowfreq 62%

basics

~20 s

Compare the roots, not the leaves. One chain is rooted at an interactive user's document application; the other at the service control manager starting a management agent as SYSTEM in session 0. Identical children, different causes, opposite verdicts.

open as a page

A Sysmon Event ID 1 record names a parent that had already exited — how do you rebuild that lineage?

level: seniorimportance: should knowfreq 44%

basics

~20 s

Join upward on the parent process GUID, never on the parent process id, because Windows reuses ids and a dead parent's number can be occupied by something unrelated. If the parent's own creation record is missing, the grandparent is unknowable and you say so.

open as a page

Desktop engineering says the RMM chain on the alerted host 'is us' — is that enough to close the case?

level: seniorimportance: nice to knowfreq 38%

basics

~20 s

Not by itself. The owner's word establishes that such automation exists, not that this particular execution was theirs. Ask for something checkable — a job record with a run identifier and window, or the script that matches the decoded argument — then close.

open as a page