XingLo SkillSearch

Windows 计划任务持久化取证

用于分析 Vista 及以后 Windows Task Scheduler 的任务 XML,排查通过计划任务实现的执行和持久化。它递归定位 Windows/System32/Tasks 下的定义,优先使用 xmllint 格式化,缺失时用 Python xml.dom.minidom,提取 Command、Arguments、Principal/UserId、触发器、运行级别、Author 和 Date 等字段,并建议通过 SOFTWARE Hive 的 TaskCache 与注册表侧记录交叉印证。Skill 明确不解析 XP/Server 2003 的二进制 .job,发现后记录为未解析缺口,避免把不同任务格式混为一谈。

在 AI 中使用此 Skill将本页链接复制给 AI,即可让 AI 获取完整 Skill 内容并按此执行
安全提示: 本站 Skill 均经 ChatGPT 最新模型扫描,未发现恶意脚本及危险指令、未检出已知恶意行为特征,但不保证绝对安全,使用即表示接受此风险

Skill 文件

版本 20260601 · 8f1d9b1ced1c5df5f838c888aa3748ac

references/
SKILL.md
---
name: dfir-scheduled-tasks
description: Parse and interpret Windows Scheduled Tasks (Vista+ Task XML). Use to find persistence and execution footholds — the command/arguments a task runs, the account it runs as, its triggers, and its registration date on a Windows asset.
---


## XingLo Standalone Package Notes

This package includes the SIFT tool-path map at `references/tools.env` for the variables used below. In the original full suite the parse-phase marker is owned by `/case-parse`; when this Skill is used independently outside that suite, the parsing commands themselves remain read-only on source evidence and write only their derived output directory.

# dfir-scheduled-tasks — Parse Scheduled Tasks (Task XML)

## Preconditions — runs inside the parse phase

This is a **parse-phase** artifact parser: it writes parsed output under `./export/`, which the
evidence guard permits **only while the phase marker `./audit/.dfir_phase` reads `parse`**. Normal use
is under `/case-parse` (or `/case-investigate`), which has already armed the parse phase — so just parse.

**The phase marker is owned solely by `/case-parse`.** `/case-parse` arms `parse` at the start and
writes `parse-complete` only once the **entire** parse phase has finished (closing the phase and
re-locking `./export/`). This skill — and every other artifact parser — must **never** write, change,
or close `./audit/.dfir_phase`: not to unblock a write, not for any reason.

**Do not stop the investigation if an `./export/` write is blocked** (guard message `BLOCKED
(evidence integrity): … outside the parse phase`, or a permission denial on an `export/` write): the
parse phase just isn't armed. Run **`/case-parse`** — the marker's owner — to arm it, then re-run the
blocked step. Do **not** set the marker yourself, and **never** reroute parsed output to `./analysis/`
to dodge the block (`./analysis/` is for analysis-phase tool runs only) — parsed evidence belongs
under `./export/` and nowhere else.

---

## Overview

Scheduled Tasks are a primary persistence and execution mechanism. This skill parses the modern
on-disk form:

- **Task XML (Vista+)** — plain-XML definitions under `Windows\System32\Tasks\` (recursive; the
  folder tree mirrors the Task Scheduler hierarchy). Each names the command/arguments, triggers, the
  principal (account/SID it runs as), and author/registration dates. No binary decoder is needed.

A second, registry-side view of the same tasks lives in the SOFTWARE hive under
`...\CurrentVersion\Schedule\TaskCache\Tasks` (reach it via `/dfir-registry`, RegRipper `taskcache`)
— use it to corroborate the on-disk XML, not as a replacement.

> **Out of scope: legacy `.job` (Task Scheduler 1.0, XP/Server 2003).** Binary `.job` files under
> `Windows\Tasks\` are **not** parsed by this skill. They are absent on Vista+ assets. If a `.job`
> file is found on an asset (see the detection in Step 0), treat it as an **unparseable** artifact:
> log it in `./audit/artifact_failures.log` and surface it in the report's Gaps / Unknowns section.

**Task-XML normalization:** `xmllint --format` when present, else `python3` (`xml.dom.minidom`),
falling back to a raw copy only on malformed XML.

---

# ── PART 1 · PARSING (used by /case-parse) ──

## Case Path Convention

`<source-dir>` is the operator-created mount subdirectory under `./sources/<asset_id>/`, used
**verbatim** — copy the real directory name exactly as it appears (e.g. `mnt-001-base-dc-cdrive`),
never a partition number or other abbreviation. The export path mirrors that name one-to-one
(`export/<asset>/<source-dir>/<artifact>/`, the canonical layout owned by `/case-parse`).

| Input | Path |
|-------|------|
| Task XML tree | `./sources/<asset_id>/<source-dir>/Windows/System32/Tasks/` |

Output: `./export/<asset_id>/<source-dir>/scheduledtasks/`
Output filename: `<asset_id>-<source-dir>-<scope>-<tool>.<ext>`. All input comes from `./sources/`.

---

## Parsing Steps

### 0. Locate the task directories (case-insensitive) — run first
```bash
SRC="./sources/<asset_id>/<source-dir>"
TASKSXML="$(find "$SRC" -ipath '*/Windows/System32/Tasks' -type d 2>/dev/null | head -1)"
SOFTWARE="$(find "$SRC" -ipath '*/Windows/System32/config/SOFTWARE' -type f 2>/dev/null | head -1)"
echo "xml: ${TASKSXML:-none}   software: ${SOFTWARE:-none}"

# Legacy .job is out of scope (see Overview). Detect any so they aren't silently missed:
JOBS="$(find "$SRC" -ipath '*/Windows/Tasks/*.job' -type f 2>/dev/null)"
if [ -n "$JOBS" ]; then
  echo "WARNING: legacy .job file(s) present but not parsed by this skill — log as a gap:"
  echo "$JOBS"
  # Per failure handling: record as unparseable in ./audit/artifact_failures.log (single-line printf).
fi
```
`find -ipath` resolves any casing of these paths — see the case-insensitive convention in
`/case-parse`. Steps below use `$TASKSXML`/`$SOFTWARE`; re-resolve if you run a block standalone.

### 1. Normalize Task XML (Vista+, primary)
Pretty-print every task into one reviewable file, preserving its tree path:
```bash
mkdir -p "./export/<asset_id>/<source-dir>/scheduledtasks"
OUT="./export/<asset_id>/<source-dir>/scheduledtasks/<asset_id>-<source-dir>-scheduledtasks-xmllint.txt"
find "$TASKSXML" -type f 2>/dev/null | while read -r t; do
  printf '\n===== %s =====\n' "$t" >> "$OUT"
  # --encode UTF-8 forces UTF-8 output: real Task XML is UTF-16, and without this
  # xmllint preserves UTF-16, garbling the UTF-8 export file. minidom emits UTF-8 too.
  xmllint --format --encode UTF-8 "$t" >> "$OUT" 2>/dev/null \
    || python3 -c 'import sys,xml.dom.minidom as m; sys.stdout.write(m.parse(sys.argv[1]).toprettyxml(indent="  "))' "$t" >> "$OUT" 2>/dev/null \
    || printf '!! NOT WELL-FORMED XML — %s bytes — original preserved in sources at the path above — FLAG FOR ANALYSIS\n' \
         "$(wc -c < "$t")" >> "$OUT"   # don't inline raw bytes: a task file that isn't XML is an irregularity
done
```
Expected output: one text file concatenating every task definition, each headed by its source path.

### 2. Corroborate from the registry (cross-source)
```bash
$REGRIPPER \
  -r "$SOFTWARE" \
  -p taskcache \
  > "./export/<asset_id>/<source-dir>/scheduledtasks/<asset_id>-<source-dir>-taskcache-regripper.txt" 2>/dev/null
```

---

## Fallback Tool

Task XML normalization has its own built-in ladder (`xmllint` → `python3` → raw `cat`, see Step 1),
and the SOFTWARE-hive `taskcache` plugin (`/dfir-registry`) is the corroborating cross-source. There
is no further automated fallback — if the XML pass yields no output, the artifact is **unparseable**:
log it in `./audit/artifact_failures.log` and surface it in the report's Gaps / Unknowns section.
Legacy `.job` files are out of scope (see Overview) and, if present, are themselves logged as a gap.

---

## Parsing Notes

- Task XML files are plain XML, so the "parser" is a normalizer, not a decoder. The ladder is
  `xmllint --format` → `python3` (`xml.dom.minidom`) → a `FLAG FOR ANALYSIS` marker: `xmllint` is
  preferred when installed (`libxml2-utils`), but `python3` is guaranteed on SIFT, so a bare host
  still normalizes rather than failing for every file. A flagged entry therefore means the file was
  genuinely not well-formed XML, not that the normalizer tool was missing.
- When both normalizers reject a file, the skill does **not** inline its raw bytes (binary/null bytes
  would corrupt the UTF-8 concatenation and bury the anomaly). It writes a one-line marker citing the
  byte count; the pristine original stays in `./sources/` at the path in the `=====` header, where
  Phase 2 reads it directly or re-extracts via TSK `icat`. A non-XML file at a task path is itself an
  irregularity — see Part 2.
- The tool router has **no** scheduled-task entry, so the router fallback tier is a no-op here.

---

# ── PART 2 · ANALYSIS (used by /case-analyze) ──

## Key Fields

Pull these from each Task XML definition:

| XML path | Meaning |
|----------|---------|
| `Actions/Exec/Command` | The binary/script the task runs |
| `Actions/Exec/Arguments` | Command-line arguments (watch for encoded PowerShell, `-enc`) |
| `Principals/Principal/UserId` | Account/SID the task runs as (SYSTEM, a user SID) |
| `Principals/Principal/RunLevel` | `HighestAvailable` = elevated |
| `RegistrationInfo/Author` | Who/what registered the task |
| `RegistrationInfo/Date` | Task registration timestamp |
| `Triggers/*` | When it fires (logon, boot, time, event) |
| `Settings/Hidden` | `true` = hidden from the Task Scheduler GUI |

---

## Interpretation & Analysis

- **Command path is the strongest signal:** a `Command` pointing at `\Temp\`, `\AppData\`,
  `\Users\Public\`, `\ProgramData\`, or invoking `powershell -enc`, `rundll32`, `regsvr32`, `mshta`,
  or a script in an odd path is a high-confidence persistence lead.
- **Registration date in the incident window** is a key indicator; pre-incident registrations are
  presumed baseline unless tied to the incident. Compare `RegistrationInfo/Date` with the TaskCache
  `Date` value — a mismatch or a task in one source but not the other is suspicious.
- **Run-as account:** `Principals/Principal/UserId` = the SID the task executes under; record the
  SID/account (map via SAM), never a person. SYSTEM tasks triggering on boot/logon are common
  attacker footholds.
- **Hidden tasks:** `Settings/Hidden = true`, or a task present in TaskCache but with no XML file (or
  vice versa), indicates GUI-evasion — this is exactly why the registry cross-source matters.
- **Triggers:** logon/boot triggers = persistence; one-shot time triggers near the incident window
  may indicate staged execution.
- **Non-XML file at a task path (parse-phase `FLAG FOR ANALYSIS` marker):** a file under
  `System32\Tasks\` that isn't well-formed XML is an objective irregularity — task definitions are
  XML. Binary/PE content (e.g. PE headers, DLL imports, RPC NDR stubs) at a legitimate-looking task
  path is a Phase-2 lead, not a conclusion. Corroborate before escalating: re-extract the bytes with
  TSK `icat` to rule out mount/extraction corruption, compare against the TaskCache registry entry
  (present in one source but not the other?), and check the `$SI`/`$FN` MFT times for the file. Treat
  as `low` confidence until a second source agrees.