Hunting for living-off-the-land persistence
A hunt is only worth the time it took if it turns into something repeatable. This one started as a manual sweep for suspicious scheduled tasks created outside business hours, and ended as a standing detection once the pattern held up across three separate incidents.
The starting hypothesis
Scheduled tasks are one of the most common living-off-the-land
persistence mechanisms because they require no additional
tooling — schtasks.exe and the Task Scheduler
COM API ship on every Windows host. The hypothesis was narrow on
purpose: an attacker with initial access but not yet elevated
privileges will often create a scheduled task using their
current, non-administrative context, pointed at a payload
living somewhere other than a standard installation directory.
That's a much more specific claim than "alert on scheduled task creation," which fires constantly from software installers, patch management, and legitimate IT automation. The hunt needed a hypothesis specific enough to be wrong, so that testing it against data would actually mean something.
Data sources and the first pass
Windows Security Event 4698 (scheduled task creation) carries
both the creating user's token and the full task action,
including the target executable path. Sysmon Event ID 1 for the
subsequent schtasks.exe or
taskeng.exe execution adds parent process context
that 4698 alone doesn't provide. The first pass hunt query
joined those two sources and filtered for tasks whose action
path fell outside Program Files,
Program Files (x86), and System32.
That first pass returned about 40 results across a month of data in the pilot environment — still too many to call a detection, but small enough to review by hand. Roughly a third were a legitimate internal deployment tool that happened to stage its binaries in a user's AppData folder, which became the first allowlist entry.
The pattern that actually held up
The generalizable part wasn't the specific task name or binary — those changed every time. It was the combination of "task created by a token that is not a member of the local Administrators group" plus "task action points at a path outside Program Files/System32." That combination had a near-zero false-positive rate across six months of production data once the internal deployment tool was excluded, which is what actually justified turning it into a standing rule instead of a one-off hunt query.
detection:
selection:
EventID: 4698
TaskContent|contains: '\AppData\'
filter_admin_context:
SubjectUserSid|contains: 'S-1-5-32-544'
filter_known_tool:
TaskContent|contains: '\InternalDeployTool\'
condition: selection and not (filter_admin_context or filter_known_tool)
What made it stick across three incidents
The rule caught genuine persistence attempts in three unrelated incidents over the following two quarters, each using a different payload and task name, which is the actual evidence that the underlying pattern — rather than an incident-specific indicator — was the right thing to detect. Each time it fired, the IR team's findings fed back into the allowlist and the filter logic, which is the part of the process that's easy to skip once a hunt "graduates" to a standing rule.
A hunt that becomes a rule isn't finished; it just moves into the same tuning lifecycle every other detection goes through. Treating the transition as a one-time event, rather than the start of ongoing maintenance, is how yesterday's good hunt becomes tomorrow's untrusted noisy rule.
Related reading:
Comments