Init
This commit is contained in:
@@ -0,0 +1,274 @@
|
||||
# Extending beyond a file
|
||||
|
||||
**What this answers:** how to change this repository's own machinery, rather than add another
|
||||
document to it: a new class of artifact, a reference family, the frontmatter or the metadata
|
||||
contract, the budget that governs the always-loaded file, a new gate and its CI wiring, and the
|
||||
procedure for demoting a claim when a measurement disagrees with it.
|
||||
|
||||
**When to load this:** when the change you want is not covered by the extension points in
|
||||
[CONTRIBUTING.md](../CONTRIBUTING.md), or when it changes what a green build means.
|
||||
|
||||
Everything here rests on one distinction. Part of this repository's contract is enforced by scripts
|
||||
at the root; the rest can only be judged by a reader. A change that moves a rule from the second half
|
||||
into the first is a real improvement, and a change that looks machine-checked but is not is how a
|
||||
guarantee rots. Where a class is maintained by a reader, this document says so.
|
||||
|
||||
## Ordinary, or a discussion first
|
||||
|
||||
| Change | Status | Why |
|
||||
|---|---|---|
|
||||
| A reference, a script, a pitfall entry, a case precedent, a measurement record, a capability row | Ordinary. Follow `CONTRIBUTING.md` | Each has a registration point and a gate that notices if the registration is missing |
|
||||
| A new gate script, additive and read-only | Ordinary, if it cannot fail a change that used to pass | A gate that only adds a signal is cheap to review |
|
||||
| Wiring an existing gate into CI | Ordinary | The sequence is in `.github/workflows/ci.yml`, and a missing gate is a visible gap |
|
||||
| Recording a downgrade of a claim | Ordinary, and expected | The repository states that a measurement which disagrees re-opens the conclusion |
|
||||
| A reference family (a subdirectory with its own index) | Ordinary, with one condition stated below | Its files are not resolved by any gate, so its index is the only entry point that exists |
|
||||
| A whole new artifact class (a new tree, or a new format the tooling reads) | Discussion first | It changes what a reader must check by hand, and possibly what an installed skill ships |
|
||||
| Adding a key to the `metadata` frontmatter block | Ordinary | No gate resolves those paths, so nothing depends on the key existing |
|
||||
| Changing what an existing `metadata` key means, or the strength-label vocabulary | Discussion first | Every document in the corpus grades against that vocabulary |
|
||||
| Changing a budget threshold, or the method that measures it | Discussion first, with a measurement | A threshold moved without a measurement is a preference with a number on it |
|
||||
| Adding a new failure mode for contributors | Discussion first | A gate that blocks a reasonable change is bypassed, and then it protects nothing |
|
||||
|
||||
## What the tooling actually reads
|
||||
|
||||
Five facts about the layout decide how a new artifact is discovered, and none of them is stated in
|
||||
the file you are adding.
|
||||
|
||||
1. `check_repo.py` discovers every `skills/*/SKILL.md` up to three levels deep, and a shallower
|
||||
`SKILL.md` shadows anything nested below it, which is the installer's own rule. Adding a second
|
||||
skill needs no change to the gates.
|
||||
2. `check_repo.py` and `check_routing.py` look at `references/*.md` and `scripts/*.py|.js`
|
||||
**non-recursively**. A file in a subdirectory of either is invisible to the routing comparison.
|
||||
3. `check_budget.py` counts the corpus recursively: `references/**/*.md`, plus every file under
|
||||
`scripts/`. The corpus note fires at 110 files or 26,000 lines, so a family is visible to the
|
||||
budget before it is visible to routing.
|
||||
4. The path resolver patterns match `references/<name>.md` and `scripts/<name>.py` with no separator
|
||||
anywhere in the name, so a nested path such as `references/coverage/route-inventory.md` is not
|
||||
resolved by any gate. A dangling nested pointer fails nothing.
|
||||
5. `check_commands.py` scans `README.md` and every `.md` under `skills/` and `docs/`, skipping
|
||||
directories named `fixtures`. A command in a nested file is still validated; a command in a file
|
||||
that no walker reaches is not.
|
||||
|
||||
The consequence is the rule for everything below: a new class needs one file that an existing gate
|
||||
already walks, and that file must state honestly what is checked and what is not.
|
||||
|
||||
## Adding a whole new artifact class
|
||||
|
||||
A class is a set of files with a shared shape that a reader is expected to keep consistent: the
|
||||
evidence companions, the coverage inventory and the case library are three examples of what is
|
||||
already here.
|
||||
|
||||
1. **Decide whether it ships.** Inside `skills/exe-reverse/` is shipped to anyone who installs the
|
||||
skill; the repository root is maintenance tooling. `README.md`'s structure block states the split,
|
||||
and a class that ships belongs in that block with an explicit `skills/exe-reverse/...` path,
|
||||
because the README's path check fails a bare relative path.
|
||||
2. **Find out whether any gate walks it.** If one does, its completeness is machine-checked and the
|
||||
class can rely on that. If none does, the class's own index must say so in a sentence a reader can
|
||||
act on, because a machine-checked guarantee that does not exist is the failure this repository
|
||||
keeps recording.
|
||||
3. **Give the class an index that is discovered.** The cheapest reliable form is a top-level
|
||||
`references/<class>.md` that opens with the head shape (the title, a goal line, and
|
||||
`## When to load this`), registers itself in `routing.md`'s Reference index, and is named in
|
||||
`routing.md`'s Sub-indexes table as the class's index, which is how the coverage inventory and the
|
||||
case library are already registered.
|
||||
4. **State the row contract in the index**, not in the individual files: what each entry must carry,
|
||||
which document holds the claim it supports, and which grade applies.
|
||||
5. **Add the class's own test only if it has a mechanical part.** A class of prose files has nothing
|
||||
to test, and a test that asserts a file exists is not a guarantee about its content.
|
||||
|
||||
Two things to avoid. A class whose only index is named inside another document's prose is not
|
||||
reachable, because nothing compares prose to a directory listing. And a class that duplicates a
|
||||
registration duty that routing already owns will drift from it.
|
||||
|
||||
## Adding a reference family
|
||||
|
||||
A family is a directory of related files with an index, and it is worth the extra structure exactly
|
||||
when the files are read together and would be misleading in the flat library.
|
||||
|
||||
1. Create `skills/exe-reverse/references/<family>/` and write `README.md` as the index: what the
|
||||
family is for, when to load it, the register or table of its members, and the template a new
|
||||
member follows. Copy the shape of `references/precedents/README.md`, which is both an index and a
|
||||
template.
|
||||
2. Register the family index in `routing.md`, in the Sub-indexes table, as
|
||||
`references/<family>/README.md`, saying what the file holds.
|
||||
3. If the family needs a top-level touchpoint as well (a symptom, a route), add that row to
|
||||
`SKILL.md` and to the mirror in `routing.md` in the same pass; the routing gate compares those two
|
||||
tables row by row and fails when one side has a row the other lacks.
|
||||
4. Add each member to the family's own register at the same time you add the file, never later. The
|
||||
register is the only reachability that exists for a nested file.
|
||||
5. Say in the index that the members are not resolved by any gate, so a reviewer knows the index must
|
||||
be read against the directory. This is a one-sentence honesty cost that makes the gap visible
|
||||
instead of implied.
|
||||
|
||||
Members of a family are still covered by the checks that scan every markdown file under `skills/`: a
|
||||
`python <script>.py` command in one of them is validated against the script's argument table, and a
|
||||
cross-file section reference is resolved against the target's real headings. `check_refs.py` reads
|
||||
`README.md` and `skills/` only, so the same content placed under `docs/` is not checked that way.
|
||||
|
||||
## Evolving the frontmatter or the metadata contract
|
||||
|
||||
**What is enforced.** `check_repo.py` parses the frontmatter with a real YAML parser, because
|
||||
frontmatter that satisfies a pattern can still be rejected by the parser the installer uses. It then
|
||||
requires `name` to equal the skill's directory name and to match lowercase alphanumerics joined by
|
||||
single hyphens, at most 64 characters, and `description` to be between 40 and 1024 characters. A
|
||||
description whose value contains a colon followed by a space must be quoted; an unquoted one makes
|
||||
the whole skill invisible to the installer. Where PyYAML is not installed the gate prints a warning
|
||||
and falls back to a colon test, so the check degrades rather than disappearing.
|
||||
|
||||
**What is not enforced.** The `metadata` block. `capability_registry`, `host_probe`,
|
||||
`evidence_summary` and `strength_labels` are promises about paths and vocabulary, and no gate
|
||||
resolves any of them. A renamed file whose path still appears there is drift nothing will report, so
|
||||
a rename has to be carried by hand and reviewed. Treat the block as documentation that happens to live
|
||||
inside a parsed structure.
|
||||
|
||||
**The description is a loading decision, not a summary.** It is the only text an agent sees when
|
||||
deciding whether this skill applies to its task. Adding a route to it is a budget decision against
|
||||
the 1024-character ceiling, and the routes that earn their characters are the ones a wrong answer
|
||||
would otherwise be given for.
|
||||
|
||||
**Changing the strength-label vocabulary needs a discussion**, because every reference, every
|
||||
evidence row and every case grades against `observed | inferred | unverified`. The burden of proof is
|
||||
that the existing three cannot express a distinction that changes a decision a reader makes. If a new
|
||||
label is genuinely needed, the change is mechanical in its extent and semantic in its risk: every
|
||||
document that states a grade has to be revisited in the same pass, and the frontmatter key that names
|
||||
the vocabulary has to be updated with them.
|
||||
|
||||
## Changing the always-loaded budget
|
||||
|
||||
`SKILL.md` loads in full whenever the skill activates, so the budget is measured on the whole file,
|
||||
including its index tables. `check_budget.py` reports:
|
||||
|
||||
| Measure | Soft | Hard | Fails the gate? |
|
||||
|---|---|---|---|
|
||||
| `SKILL.md` lines | 460 | 560 | at the hard limit |
|
||||
| `SKILL.md` tokens | 13000 | 16000 | at the hard limit |
|
||||
| Index-row description length | 250 characters | 400 | never: both branches are warnings |
|
||||
| Long reference with no navigable head | 100 lines, head checked in the first 40 | not applicable | never: a warning |
|
||||
| Corpus size | 110 files, 26000 lines | not applicable | never: a note |
|
||||
|
||||
The token count comes from `tiktoken`'s `cl100k_base` encoding when it is installed, and otherwise
|
||||
from `characters / 4.285`. That fallback constant is calibrated against the real measurement rather
|
||||
than guessed, and it exists for a stated reason: an earlier value made the gate reach **different
|
||||
verdicts on different machines**, reporting an over-budget file where the tokenizer was absent and a
|
||||
compliant one where it was present. The gate's answer must not depend on what the runner happens to
|
||||
have installed, which is also why CI installs the tokenizer so the real path is exercised.
|
||||
|
||||
**To change a threshold:** produce the measurement that justifies the new number, keep the two
|
||||
measurement paths agreeing, and update the numbers and the calibration note in the docstring in the
|
||||
same change, because the docstring is the only place the calibration is recorded. A threshold that
|
||||
moves because a document outgrew it is not a change to the budget; it is the budget being abandoned.
|
||||
|
||||
**The ordinary answer instead:** move the detail into a reference and leave a pointer. Two things
|
||||
stay in the always-loaded body, and both are argued in the gate's own comment: the symptom index,
|
||||
because a symptom-to-file lookup must stay one hop, and the gates with their pass criteria, because
|
||||
"the idea is understood" does not clear a gate.
|
||||
|
||||
**Verify with both modes:** `python check_budget.py` reports the measured values and the notes;
|
||||
`python check_budget.py --strict` turns every warning into a failure, which is how a warn-only rule
|
||||
is checked when someone chooses to hold the line.
|
||||
|
||||
One known wording defect, reported here rather than copied: the docstrings of `check_routing.py` and
|
||||
`check_budget.py` describe "the four gates with their pass criteria", while the entry point defines
|
||||
five (G1 to G5). Comment text is checked by no gate. If you touch either file, correct the wording
|
||||
instead of reproducing it.
|
||||
|
||||
## Adding a new gate script and wiring it into CI
|
||||
|
||||
**The shape a gate has here.** A root-level `check_<name>.py`, runnable from the repository root with
|
||||
no arguments as `check_<name>.py` under the interpreter, printing a human-readable summary and, where a caller needs
|
||||
to branch on the verdict, a final `RESULT=<token>` line. Exit 0 when the tree satisfies the rule and
|
||||
non-zero when it does not. `--json` is the convention for machine-readable output, and the one place
|
||||
it is deliberately absent from the text stream is a JSON payload: a `RESULT=` line appended to JSON
|
||||
makes the stream unparseable for the caller that asked for JSON, so there the status is a field and
|
||||
the exit code carries the meaning. `check_repo.py` and `check_refs.py` take no arguments at all;
|
||||
`check_routing.py` takes only `--json`; `check_budget.py` adds `--strict`; `check_commands.py` adds
|
||||
`--path`, `--fix-report`, `--include-workbench` and `--quiet`.
|
||||
|
||||
**Rules a gate follows here:**
|
||||
|
||||
1. **Read-only.** A gate must not modify the tree. The only root tool that writes is
|
||||
`build_scripts.py`, and it writes only under `--apply`, after an audit pass that is the default.
|
||||
2. **Report the boundary of the check.** `check_commands.py` counts the invocations it could not
|
||||
verify rather than skipping them silently, because "no finding" from an interface it cannot read
|
||||
would be indistinguishable from coverage. A new gate that cannot see part of its surface should
|
||||
say so in its own output.
|
||||
3. **Say when the surface is empty.** A gate that found nothing to check has not passed; it has run
|
||||
against nothing. `scan_leaks.py` has a dedicated exit code for exactly this.
|
||||
4. **Prefer a narrow rule with a stated exemption over a broad rule that is wrong.** The gates here
|
||||
carry their exemptions in a table with a reason per entry, because a gate that gets switched off
|
||||
protects nothing.
|
||||
5. **Do not restate another gate's rule without saying why.** Measured duplication exists here: the
|
||||
routing gate and the budget gate both check that a reference is reachable. The second copy is
|
||||
justified in a comment, because a dead reference is invisible in a way a broken path is not.
|
||||
6. **Warn, do not fail, when the gate cannot judge.** Hedge detection, restatement and corpus size
|
||||
are notes; index-row length and navigability are warnings; only the ceilings fail. `--strict` is
|
||||
the opt-in that makes warnings fatal.
|
||||
|
||||
**Every gate needs a test.** A gate gets a subprocess contract test under `tests/cli/`: the flags it
|
||||
accepts, the exit code per verdict, and the `RESULT=` token where it prints one. A mis-typed marker
|
||||
is a harness failure rather than a warning, because the suite runs with `--strict-markers`.
|
||||
|
||||
**Lint and typing are free.** The CI lint job runs `mypy` and `flake8` over `*.py` at the root plus
|
||||
the skill's scripts, so a new root gate is checked without a configuration change. Both tools read
|
||||
their configuration from the repository root, and the job deliberately runs on an interpreter with
|
||||
none of the optional dependencies installed, so a new gate must import them lazily or behind a
|
||||
fallback with a stated behaviour.
|
||||
|
||||
**Wiring it in.** Add the step to the `test` job in `.github/workflows/ci.yml`, in the gate sequence
|
||||
and before the regression suite. There is deliberately no `paths:` filter on the workflow: a filter
|
||||
that watches only source files would skip the structural gates on exactly the commits they exist to
|
||||
catch, which are documentation commits. A gate that can only run where a device, a debugger or a
|
||||
network peer exists does not belong in CI; record that route in the measurement record instead and
|
||||
let the test self-skip with a printed reason.
|
||||
|
||||
**Document it.** `README.md`'s repository-maintenance list names every root tool with what it
|
||||
enforces, one line each. A tool that is not in that list is invisible to a contributor who never
|
||||
opens the root directory.
|
||||
|
||||
**What needs a discussion first:** a gate that can fail a change which used to pass, a gate whose
|
||||
rule is a matter of taste rather than a checkable property, and a gate that duplicates a rule another
|
||||
gate already owns.
|
||||
|
||||
## Recording a downgrade when a measurement disagrees
|
||||
|
||||
The stop condition is stated in the entry point: a measurement that disagrees with a conclusion
|
||||
recorded as settled re-opens the conclusion, and the measurement is not explained away. That is a
|
||||
rule about behaviour; this is the procedure that makes it a change a repository can hold.
|
||||
|
||||
1. **Freeze the measurement first.** The command as it was run, its output, the artifact digest, the
|
||||
tool and its version, and the host build. Without those pins the disagreement is an anecdote, and
|
||||
it cannot raise or lower anything.
|
||||
2. **Name the link that was weaker than recorded.** A chain is graded by its weakest link, so the
|
||||
question is not "was the conclusion wrong" but "which step was assumed". A tool that reports
|
||||
success is evidence about the tool; it was never evidence about the target.
|
||||
3. **Move every document that states the claim, in one pass.** The coverage row
|
||||
(`references/coverage-and-limits.md`: the direction, the strongest defensible grade, what it rests
|
||||
on, and the dependency that is not shipped here), the evidence row
|
||||
(`references/evidence-summary.md`), and the machine-readable row under `skills/exe-reverse/evidence/`.
|
||||
A downgrade that lands in one of the three leaves the other two asserting the stronger grade, and
|
||||
the stronger grade is the one a reader will find.
|
||||
4. **Record the negative result.** `docs/tool-verification/` is where the pass goes, with the command
|
||||
and its output, and with the scenarios the target could not exercise named. A route measured and
|
||||
failed on a named host is more useful than a route nobody ran.
|
||||
5. **Add a pitfall entry only if the downgrade has a mechanism.** The catalogue's value is the
|
||||
mechanism that made the failure invisible, not the failure. If the earlier claim looked right for
|
||||
a reason a reader could hit, that reason is an entry; if the claim was merely optimistic, it is
|
||||
not. Cite the entry by number from anywhere else rather than restating it.
|
||||
6. **Describe the demotion in the change.** The repository's own rule is that a corrected conclusion
|
||||
is described rather than quietly edited, and that a record keeps the wrong version beside the
|
||||
right one, because the retraction is the transferable part.
|
||||
|
||||
**Restate the grade for the whole chain**, not for the corrected step alone. A chain that ends in a
|
||||
step nobody executed is `inferred` at best, and a chain whose weakest link was just replaced is
|
||||
entitled to be re-graded only as far as the new weakest link.
|
||||
|
||||
**A downgrade is a result, not a failure.** The coverage file commits to this: a negative measurement
|
||||
raises a row's honesty even while it lowers its grade, and a row with no measurement behind it is
|
||||
worse than a row with a disappointing one. What a downgrade must not do is delete the earlier claim,
|
||||
edit a conclusion without saying so, or leave a downstream document stating the grade that was just
|
||||
withdrawn.
|
||||
|
||||
There is a precedent in this repository for the kind of error that forces a downgrade: the budget
|
||||
gate's token fallback constant was first set to a value that made the gate reach different verdicts
|
||||
on machines with and without the tokenizer, which is the shape of a claim that looks measured and is
|
||||
not. Its docstring now records the calibration and the reason, which is what a downgrade should leave
|
||||
behind.
|
||||
@@ -0,0 +1,70 @@
|
||||
# Extension: hardware and kernel routes
|
||||
|
||||
What this pass covered, what it verified externally, and what it deliberately did not assert. The
|
||||
routes here are the skill's reason to exist and its least portable part, so the record is organised
|
||||
around one question: **what does each mechanism actually require, and what does it still not see?**
|
||||
|
||||
## What was verified externally
|
||||
|
||||
| Fact | How it was established | Grade |
|
||||
|---|---|---|
|
||||
| The trace feature has its own capability leaf, and its bits describe filters and output modes rather than "works / does not work" | read from an operating system kernel's implementation of the same CPUID contract | `inferred` for the bit names, `observed` that the leaf answers on this host |
|
||||
| The trace unit is reachable only from privileged code | the control registers and the output region are set from ring 0 in that same implementation | `inferred` |
|
||||
| A last-branch-record capability leaf exists, and its depth field is a **mask rather than a count** | the same implementation computes depth from the mask, and published values differ by generation (4, 8, 16, 32) | `inferred` |
|
||||
| The architectural performance-monitoring leaf reports version, counter count, bit width and mask length | the same implementation reads those subfields, and treats the leaf as unusable below a minimum mask length | `inferred` |
|
||||
| A hypervisor is detectable from a single CPUID bit, and its vendor from a separate leaf | the same implementation | `inferred` |
|
||||
| Hardware breakpoints: a small fixed number of debug registers; a watch is 1, 2, 4 or 8 bytes wide (8 only on 64-bit); the address must be aligned to the width; there is **no read-only data breakpoint**; the access field encodes execute, write, or read-write | processor documentation for the register layout; the encoding rules are what the control field admits | `observed` for the count and the field set |
|
||||
| A data breakpoint set on a code address never fires | instruction fetch is not a data access, so no access-width and access-type combination can match it | `inferred`, and it is the single most common misunderstanding in this area |
|
||||
| Single-machine instruction-level recording exists as a debugger feature, is user-mode only, has a high recording overhead, produces multi-gigabyte files, and cannot record a protected process | the feature's own documentation | `observed` |
|
||||
| Local kernel debugging is documented but cannot stop the target | the documented command set excludes the halting commands while permitting memory access | `observed` |
|
||||
| Full kernel debugging over a network needs a boot-configuration change on the target and a second machine (or a virtual machine) | the documented setup procedure | `observed` |
|
||||
| Every register-level monitoring mechanism needs ring 0, and therefore an administrator and a driver — which may in turn need test signing, and may still be refused where code-integrity enforcement is active | the documented requirements for each | `inferred` |
|
||||
|
||||
## What this changed in the skill
|
||||
|
||||
Three claims that were circulating as common knowledge turned out to be wrong, and the corrections
|
||||
are now in the route text and in the probe's output:
|
||||
|
||||
- **There is no single timing packet.** Timing is carried by three different packet kinds, each
|
||||
gated by its own capability bit. A plan that assumes one timing packet will mis-parse a trace.
|
||||
- **The branch-record depth is not a constant.** Values of 4, 8, 16 and 32 all exist across
|
||||
generations, and the field is a mask. Anything that hardcodes "16 entries" is wrong for some of the
|
||||
processors it will meet.
|
||||
- **"Four breakpoints" does not mean four watchpoints of any size.** The width is restricted, the
|
||||
address must be aligned, no read-only data watch exists, and setting one on a code address does
|
||||
nothing at all.
|
||||
|
||||
And one boundary was sharpened enough to change a recommendation: **on this platform there is no
|
||||
documented user-mode path to the trace unit.** The in-box support that exists is aimed at crash
|
||||
analytics rather than at analysis, and is not documented for this purpose. That is why the probe
|
||||
reports the trace route as needing a driver or a record-and-replay debugger rather than as merely
|
||||
needing a decoder, and why `references/evidence-summary.md` grades the route `unverified` here rather
|
||||
than `inferred`.
|
||||
|
||||
## What was deliberately not asserted
|
||||
|
||||
Written down rather than guessed, because each of these is exactly the kind of detail that becomes
|
||||
authoritative-looking when it is wrong:
|
||||
|
||||
- The normative bit layout of every capability field. Where a vendor specification was not reachable,
|
||||
the encodings were taken from an implementation of the same contract and are labelled as such. A
|
||||
reader who needs the normative definition is told to consult the specification for the processor in
|
||||
front of them.
|
||||
- Byte-level encodings of the trace packet format. The packet *set* is established; the wire encoding
|
||||
of individual packets is not.
|
||||
- Sampling-precision claims, the deprecation status of the older branch-trace mechanism, which
|
||||
virtualisation exits intercept which registers, and the interaction between kernel patch protection
|
||||
and debuggers. For each of these a first-party source was not reachable from this environment, so
|
||||
the documentation names the mechanism without asserting the behaviour.
|
||||
- A numeric overhead figure for OS-level event tracing. The documented problem is loss accounting and
|
||||
a single-session constraint, which is what the text states; an overhead percentage would have had
|
||||
to be invented.
|
||||
|
||||
## Why this route is probed rather than declared
|
||||
|
||||
Every mechanism here is conditional on the host: on a supported processor, on privileges, on signing,
|
||||
on whether the analysis machine is itself virtualised. A plan that assumes otherwise fails at the first
|
||||
step and gets attributed to the tooling. `scripts/hwprobe.py` exists so that the answer is a
|
||||
measurement with three states — available, needs-`<something>`, unavailable — and so that a host
|
||||
inside a hypervisor reports that fact rather than silently degrading every route that depends on a
|
||||
debug exception.
|
||||
@@ -0,0 +1,93 @@
|
||||
# Extension: tool verification
|
||||
|
||||
How the toolchain claims in this skill were established, and which of them remain documentation only.
|
||||
|
||||
## Method
|
||||
|
||||
For each tool or mechanism the skill depends on, three questions were asked in this order:
|
||||
|
||||
1. **Is it present on the host that did the work?** A path lookup and a version call, recorded in
|
||||
`TOOL-VERDICTS.md`. Absence is a finding, not a failure.
|
||||
2. **Is what the skill says about it true?** Wherever possible from the tool's own documentation or
|
||||
the platform's own specification — not from a recollection, and not from a secondary summary.
|
||||
3. **Has the route been run?** If not, the claim is `inferred` or `unverified` and the missing step is
|
||||
named. `references/evidence-summary.md` carries the grade.
|
||||
|
||||
The order matters. A tool that is present but whose behaviour is described from memory is worse than
|
||||
an absent tool, because the description reads as verified.
|
||||
|
||||
## What is `observed` and can be re-run
|
||||
|
||||
These rest on commands whose output exists, and every one of them needs only Python plus a file on
|
||||
disk — no third-party sample, no debugger, no network:
|
||||
|
||||
- the reader parsing real platform images and generated fixtures, and reproducing the declared checksum
|
||||
on unmodified images;
|
||||
- RVA-to-file-offset translation in both directions, including the virtual-only tail that has no bytes
|
||||
on disk;
|
||||
- import, export, resource, TLS, debug and relocation parsing against fixtures that declare their own
|
||||
expected values;
|
||||
- the patch pipeline end to end: a read-only input, a new output, a recomputed checksum, every patched
|
||||
byte re-read from the written file, and the input's hash unchanged;
|
||||
- the patcher's refusals, each exercised deliberately: an ambiguous pattern, a mismatch against
|
||||
expected bytes, and a length that cannot be established;
|
||||
- pattern location with its hit counts and verdicts, including the zero-match and multi-match cases;
|
||||
- fixture generation, deterministic in bytes and parsing back through the reader;
|
||||
- the host probe's three-state route table and its raw CPUID reporting;
|
||||
- the identity scanner on this repository.
|
||||
|
||||
## What is `inferred` and why
|
||||
|
||||
A mechanism described from its documentation, with the tool absent from the development host.
|
||||
Debugging workflows, injection and hooking, dump repair, managed tooling, installer extraction,
|
||||
kernel debugging and driver loading are all in this category. For each, the reference states the
|
||||
mechanism and its requirements, and the evidence summary names what would raise the grade: a run.
|
||||
|
||||
This is the honest position, and it is deliberate rather than accidental: the alternative is to write
|
||||
procedures that have never been executed in the register of ones that have.
|
||||
|
||||
## What is `unverified` and why
|
||||
|
||||
Two kinds of thing sit here, and they are different problems.
|
||||
|
||||
**Hardware-bound routes.** The trace unit and the branch-record registers are reachable only from
|
||||
privileged code, and the host used for this work runs under a hypervisor. The probe reports what the
|
||||
processor advertises and what the platform permits; the route itself was not exercised. A reader with
|
||||
a bare-metal host and a driver can move this row, and the documentation says so rather than implying
|
||||
the route is unavailable in principle.
|
||||
|
||||
**Details whose normative source was unreachable.** Where a vendor or platform specification could not
|
||||
be opened from this environment, the detail is either omitted or labelled. The list is in
|
||||
`TOOL-VERDICTS.md` under "what is deliberately not asserted", and it exists so that a later pass
|
||||
knows exactly which gaps to close rather than re-discovering them.
|
||||
|
||||
## Corrections this pass made
|
||||
|
||||
Facts that were checked and turned out to contradict the widely repeated version. Each one is now
|
||||
stated in its corrected form where the skill needs it, because a reader acting on the common version
|
||||
builds the wrong plan:
|
||||
|
||||
- a strong name is identity, not an anti-tamper mechanism, and is not validated at load on current
|
||||
runtimes;
|
||||
- a precompiled managed image is not thereby locked — methods can still be replaced at run time,
|
||||
while a natively compiled deployment is the one that removes the readable layer;
|
||||
- a dialogs-suppressing switch on a disassembler's command line is not the switch that runs a script;
|
||||
- an undocumented build-fingerprint block in a PE header has no normative source, and a parser for it
|
||||
must say so;
|
||||
- the largest minidump configuration can carry more than a nominal "full" one, and a flag commonly
|
||||
read as "everything" is documented as unsupported for user-mode dumps;
|
||||
- a page-guard breakpoint fires once, and treating it as a watchpoint loses every access after the
|
||||
first;
|
||||
- a copy-on-write image page is still reported as an image, so distinguishing it needs the working-set
|
||||
information;
|
||||
- an administrative installation of a package removes its digital signature;
|
||||
- a certificate in the image is a statement about the digest, which is why editing the file while
|
||||
preserving the digest does not happen;
|
||||
- there is no single timing packet in the trace format, and the branch-record depth is a per-generation
|
||||
capability rather than a constant.
|
||||
|
||||
## The rule this pass produced
|
||||
|
||||
**A tool's absence is a finding, and a tool's behaviour described from memory is a defect.** The first
|
||||
is cheap to record; the second propagates as a fact. Everything in this directory exists so a reader
|
||||
can tell which of the two they are looking at.
|
||||
@@ -0,0 +1,165 @@
|
||||
# Findings
|
||||
|
||||
Defects that this repository's own tools, gates and fixtures found **in each other**, during the pass
|
||||
that built it. Each entry states the symptom, the root cause, how it surfaced, and the check that now
|
||||
catches it.
|
||||
|
||||
These are recorded because the interesting part is not the fix. It is that every one of these produced
|
||||
output that looked correct: a table of plausible flag names, a resource count that was merely large, a
|
||||
length guard that never fired. That is the failure shape this whole repository is organised against.
|
||||
|
||||
## Parser defects
|
||||
|
||||
**F1. The DLL-characteristics table was shifted by six bits, and produced a confident wrong answer.**
|
||||
|
||||
*Symptom:* parsing a stock system binary reported `no_seh, no_bind, wdm_driver, 32bit_dll`. Those
|
||||
flags on a modern executable are implausible — a user-mode program with no exception handling, marked
|
||||
as a kernel driver and as 32-bit, on a 64-bit host.
|
||||
|
||||
*Root cause:* the constant table was written from recall rather than from the specification, and every
|
||||
entry was displaced: what the code called `no_seh` was the high-entropy-VA bit, and so on through the
|
||||
whole table. The output was well-formed, self-consistent, and wrong.
|
||||
|
||||
*How it surfaced:* by parsing a **real** image whose expected flags are known, rather than by testing
|
||||
against the parser's own fixtures. A fixture built from the same wrong table would have agreed with it.
|
||||
|
||||
*Fix and check:* the values were taken from the published format specification and the table now
|
||||
records where it came from. The regression is a test asserting the exact flag **names** for a real
|
||||
binary's header, not merely that a list is non-empty.
|
||||
|
||||
**F2. The section-alignment field was treated as a set of independent bits.**
|
||||
|
||||
*Symptom:* a code section reported two alignment flags at once.
|
||||
|
||||
*Root cause:* the alignment of a section is bits [23:20] of the characteristics field — an **index**,
|
||||
not a set of bits — and the specification states the values are valid only for object files. Testing
|
||||
them one bit at a time turns the execute and read bits into two imaginary alignment flags.
|
||||
|
||||
*How it surfaced:* the same real-image run, on a section whose characteristics value is a plain
|
||||
execute|read pair.
|
||||
|
||||
*Fix and check:* alignment is decoded with a mask and the table carries the note about its scope. A
|
||||
unit test asserts that a section with execute|read reports exactly those two flags and no alignment.
|
||||
|
||||
**F3. Resource counts are 16-bit fields read as 32-bit, which inflated one resource into 478.**
|
||||
|
||||
*Symptom:* a fixture with exactly one resource entry reported 478 entries.
|
||||
|
||||
*Root cause:* the resource directory's two count fields are 16 bits each. Reading either as a 32-bit
|
||||
value swallows the first two bytes of the following entry and the walk then chases table bytes as if
|
||||
they were entries.
|
||||
|
||||
*How it surfaced:* only after the fixture generator was written. Real system images hid it — their
|
||||
counts and neighbouring bytes happen to combine into values that do not explode — while a fixture with
|
||||
exactly one entry could not.
|
||||
|
||||
*Fix and check:* both counts are read as 16-bit. The fixture declares its expected entry count and a
|
||||
test asserts the walk returns exactly that.
|
||||
|
||||
## Patcher defects
|
||||
|
||||
**F4. The ambiguity guard was defeated by its own default.**
|
||||
|
||||
*Symptom:* a pattern matching dozens of sites was silently patched at the first one.
|
||||
|
||||
*Root cause:* the command-line path always wrote an explicit hit index into the request, and its
|
||||
default value was `0`. The guard exists to refuse a multi-site match unless a site is *chosen*, and a
|
||||
default of zero counted as a choice.
|
||||
|
||||
*How it surfaced:* by testing the guard deliberately with a pattern that is obviously common (an
|
||||
indirect call sequence) instead of with a pattern known to be unique.
|
||||
|
||||
*Fix and check:* the index is carried only when it was actually supplied, and the refusal now lists the
|
||||
sites it found. A test calls the guard with the common pattern and asserts the refusal.
|
||||
|
||||
**F5. The equal-length check compared a value with itself, so it never fired.**
|
||||
|
||||
*Symptom:* the length guard existed, was documented, and could not fail.
|
||||
|
||||
*Root cause:* the "original" bytes were sliced to the length of the replacement, so the two lengths
|
||||
were equal by construction. Worse, the script cannot in general know the length of a site without
|
||||
disassembling it — which means the honest answer is not "equal" but "not established".
|
||||
|
||||
*How it surfaced:* by trying to make the guard fire. A guard that cannot fail is not a guard.
|
||||
|
||||
*Fix and check:* length is only judged when a reference exists (the expected bytes, or an explicit
|
||||
declared length), and the three states — equal, changed, unproven — are printed distinctly. A test
|
||||
asserts that a request with no reference prints the unproven marker, and that a declared length that
|
||||
disagrees with the replacement is refused. The first version also printed the unproven case as a
|
||||
length change, because a missing value is falsy; that is now a three-state marker.
|
||||
|
||||
## Fixture defects
|
||||
|
||||
**F6. `SizeOfHeaders` was computed without the section table.**
|
||||
|
||||
*Symptom:* every section's payload sat at a different file offset than the one the header declared.
|
||||
|
||||
*Root cause:* the header size calculation covered the DOS header, the PE signature, the file header
|
||||
and the optional header, and omitted the section table — 40 bytes per section. The declared value was
|
||||
therefore smaller than the real header block, and everything after it was misplaced.
|
||||
|
||||
*How it surfaced:* an assertion in the generator comparing the planted byte sequence at its declared
|
||||
file offset with the bytes actually written there. Without that assertion the file would have been
|
||||
written, would have parsed, and would have been wrong.
|
||||
|
||||
*Fix and check:* the section table is included in the calculation, with an assertion that the aligned
|
||||
size is at least the computed size. The generator asserts every structural claim it makes.
|
||||
|
||||
**F7. Two structures were placed at estimated offsets, and overlapped.**
|
||||
|
||||
*Symptom:* the debug directory was written into the region the import table occupied.
|
||||
|
||||
*Root cause:* the first version of the generator gave the import table, the debug directory and the TLS
|
||||
directory each a hand-picked offset. Two of those choices collided.
|
||||
|
||||
*How it surfaced:* while computing where the import table actually ended, which is not something one
|
||||
does while *writing* estimated offsets.
|
||||
|
||||
*Fix and check:* the payload is laid out with a single cursor that allocates each structure in turn and
|
||||
refuses to overflow the buffer. An overlap is now impossible by construction rather than by care.
|
||||
|
||||
**F8. The debug-directory structure had the wrong field count, and a file offset was filled with an RVA.**
|
||||
|
||||
*Symptom:* the generator failed to build, then — once it built — the record could not be located.
|
||||
|
||||
*Root cause:* the structure has eight fields, not seven, and its last field is a **file offset** while
|
||||
the field before it is an RVA. Two different address spaces inside one 28-byte structure.
|
||||
|
||||
*Fix and check:* both fields are written correctly and the reader resolves the PDB path from the file
|
||||
offset, which is a round-trip the test asserts.
|
||||
|
||||
## Probe defects
|
||||
|
||||
**F9. A pseudo-handle was passed as a 32-bit integer, and the probe looked like a policy denial.**
|
||||
|
||||
*Symptom:* the privilege probe reported that opening the process token failed with error 6
|
||||
(`ERROR_INVALID_HANDLE`) on an elevated session.
|
||||
|
||||
*Root cause:* the pseudo-handle for the current process is `(HANDLE)-1`. Without an explicit pointer
|
||||
return type the binding layer narrows it, the call receives the wrong value, and the failure looks
|
||||
exactly like a machine where the operation is not permitted.
|
||||
|
||||
*How it surfaced:* by disbelieving the result. "Elevated session cannot query its own token" is not a
|
||||
plausible measurement.
|
||||
|
||||
*Fix and check:* the call signature declares its types, and a test asserts the probe reports elevation
|
||||
on this host rather than an error.
|
||||
|
||||
**F10. Long values were truncated by the printer.**
|
||||
|
||||
*Symptom:* a raw capability value printed as `0x000`, losing its significant digits.
|
||||
|
||||
*Root cause:* a fixed column width applied to a value string. The data was correct; the report was not.
|
||||
|
||||
*Fix and check:* long values wrap onto continuation lines instead of being cut. A test asserts a long
|
||||
value survives into the output intact.
|
||||
|
||||
## What this list is for
|
||||
|
||||
Ten defects, all in code written during this pass, all of which produced output that looked right. The
|
||||
pattern is consistent enough to state as a rule:
|
||||
|
||||
**A check that cannot fail is not a check, and a table written from recall is a table that is wrong in
|
||||
one place you will not notice.** Both are why this repository prefers a value taken from a
|
||||
specification with its source named, an assertion over an estimate, and a fixture that can disagree
|
||||
with the tool reading it.
|
||||
@@ -0,0 +1,56 @@
|
||||
# Tool Verification
|
||||
|
||||
**This directory is not part of the installed skill.** It is the evidence record for measurement
|
||||
passes: what was actually run, on what, against which independent method, and what the target could
|
||||
not exercise. It exists so the coverage claims in the skill can be checked against runs instead of
|
||||
being trusted.
|
||||
|
||||
**When to read it:** when a claim's strength decides whether you rely on it, or when you are
|
||||
extending the skill and want to know whether a route has ever been exercised.
|
||||
|
||||
## What is in here
|
||||
|
||||
| File | What it holds |
|
||||
|---|---|
|
||||
| `FINDINGS.md` | defects that this repository's own tools, gates and fixtures found in each other — each with its root cause and the check that now catches it |
|
||||
| `TOOL-VERDICTS.md` | per-tool verdicts: present or absent on the development host, what was verified about it from a primary source, and what remains documentation-only |
|
||||
| `REPO-DECISIONS.md` | the design decisions behind the layout, the strength vocabulary, and the gates, with the reason each was chosen |
|
||||
| `EXTENSION-*.md` | one file per research pass: what the pass covered, what it verified externally, and what it deliberately did not assert |
|
||||
|
||||
## How to read a record here
|
||||
|
||||
Three things make a row usable, and a row missing any of them should be treated as a note rather than
|
||||
as evidence:
|
||||
|
||||
1. **The command.** Not "the reader was tested" — the exact invocation, so anyone can re-run it.
|
||||
2. **The output that matters.** The decisive line, not a log dump.
|
||||
3. **The independent method.** A second way of arriving at the same answer. A parser agreeing with
|
||||
itself is not a check; a parser agreeing with the value the file declares, or with an independent
|
||||
tool, is.
|
||||
|
||||
## The rules this directory obeys
|
||||
|
||||
- **A claim carries its grade.** `observed` (a command and its output exist), `inferred` (reasoned
|
||||
from an observation), `unverified` (assumed, or reported elsewhere and never reproduced here).
|
||||
- **A limit cites the measurement that set it.** A limit without a constraint behind it is a
|
||||
preference.
|
||||
- **Missing coverage is `unknown`, never absence.** "The tool was not found" and "the tool does not
|
||||
exist" are different statements; the first names the search.
|
||||
- **A measurement that disagrees with a recorded conclusion re-opens it.** The conclusion is not
|
||||
quietly edited; the change is described.
|
||||
- **Nothing here names a target.** Binaries used for measurement are either built by this repository
|
||||
or are platform images that exist on any Windows installation; no third-party sample is required to
|
||||
reproduce a row, and none is committed.
|
||||
|
||||
## The relationship to `tests/`
|
||||
|
||||
They are different artifacts and neither substitutes for the other.
|
||||
|
||||
- `tests/` asserts what the **scripts** do. It runs on a bare checkout, offline, in seconds, and it is
|
||||
what CI gates on.
|
||||
- `docs/tool-verification/` records what a **route** did against a real artifact, including the routes
|
||||
that cannot run in CI because they need a debugger, a driver, a second machine or hardware support.
|
||||
|
||||
A green test suite means the parsers and planners behave as documented. It says nothing about whether
|
||||
a hardware route works on your machine, which is why `scripts/hwprobe.py` exists and why the hardware
|
||||
rows in `references/evidence-summary.md` are graded `inferred` or `unverified` with what is missing.
|
||||
@@ -0,0 +1,145 @@
|
||||
# Repository Decisions
|
||||
|
||||
Why the repository is shaped the way it is. Each entry records the alternative that was considered
|
||||
and the reason it lost, because a decision with no recorded alternative gets re-litigated by every
|
||||
future contributor.
|
||||
|
||||
**When to read it:** before proposing a structural change, and when an existing rule looks arbitrary.
|
||||
|
||||
## Layout
|
||||
|
||||
**Decision: a skills repository with the skill under `skills/<name>/`, and everything else as
|
||||
maintenance tooling.**
|
||||
|
||||
The alternative was a flat repository where the skill is the root. It loses on one specific point: the
|
||||
gates, the tests and the evidence record are *about* the skill rather than part of it, and installing
|
||||
the skill should not install them. The `skills/<name>/SKILL.md` layout is also what the installer
|
||||
resolves, so the structure and the install mechanism are the same fact stated once.
|
||||
|
||||
**Decision: an agent preset is not involved, and no runtime is required.**
|
||||
|
||||
The skill is documents plus scripts. A background process, a server, or a plugin would each add a
|
||||
failure mode that cannot be checked by reading a file, and none of them is needed to answer the
|
||||
questions this skill answers.
|
||||
|
||||
## The strength vocabulary
|
||||
|
||||
**Decision: three grades — `observed`, `inferred`, `unverified` — applied to every factual claim.**
|
||||
|
||||
The alternative was ordinary prose, which reads as uniformly confident. The observed failure is not
|
||||
that a reader disbelieves a claim; it is that a reader cannot tell a measured result from a plausible
|
||||
one, and therefore builds a plan on top of an assumption.
|
||||
|
||||
Three grades rather than two, because the interesting middle case is real: a mechanism verified from
|
||||
its documentation, exercised by nobody here. Calling that "unverified" hides that the mechanism is
|
||||
known; calling it "verified" is a lie.
|
||||
|
||||
**Decision: missing coverage is recorded as `unknown`, never as absence.**
|
||||
|
||||
A search that returned nothing is evidence about the search. "The tool is not installed" and "the tool
|
||||
does not exist" lead to different actions, and collapsing them is the most repeated data error in this
|
||||
kind of record.
|
||||
|
||||
**Decision: a claim is only as strong as the weakest link in its chain.**
|
||||
|
||||
Stated separately because it is violated by composition rather than by any single sentence: a row that
|
||||
joins a verified mechanism to an unexercised route reads as one verified claim unless the joining is
|
||||
called out.
|
||||
|
||||
## The entry point
|
||||
|
||||
**Decision: `SKILL.md` stays near 300 lines, and the inventory lives in `references/routing.md`.**
|
||||
|
||||
The alternative was one large entry point. It loses because the entry point loads in full on every
|
||||
activation: a lookup table that is read once per task is a poor use of that budget, while a
|
||||
symptom-to-file mapping must stay one hop and therefore belongs there.
|
||||
|
||||
**Decision: the symptom index is mirrored into `routing.md`, with a gate comparing the two.**
|
||||
|
||||
Splitting a table is how two tables start disagreeing. A mirror plus a check gives a reader all three
|
||||
tables in one load and makes drift a build failure rather than a slow decay.
|
||||
|
||||
**Decision: the entry point is a procedure with gates, not background reading.**
|
||||
|
||||
Because the observed failure mode is a reader agreeing with the material and then reasoning from
|
||||
first principles anyway. Gates are actions with pass criteria precisely so that "I understand the
|
||||
idea" cannot be mistaken for having done it.
|
||||
|
||||
## The scripts
|
||||
|
||||
**Decision: standard library only, Python 3.9+, no third-party imports.**
|
||||
|
||||
A parser used to build a toolchain must be readable and must run on a bare install, including in a
|
||||
locked-down analysis VM. A dependency is a thing that can fail to install at the moment the analysis
|
||||
is happening. Where a third-party tool is the right instrument, the script reports its absence
|
||||
instead of substituting a weaker method.
|
||||
|
||||
**Decision: every structure offset is computed, never estimated.**
|
||||
|
||||
Two structures in the fixture generator overlapped because each had a hand-picked offset. A single
|
||||
allocating cursor makes the overlap impossible rather than merely absent.
|
||||
|
||||
**Decision: a numeric table carries its source.**
|
||||
|
||||
A constant table written from recall was wrong in every entry and produced plausible output. Tables
|
||||
taken from a specification name the specification; tables that read an undocumented structure say so.
|
||||
|
||||
**Decision: an unproven property is reported as unproven, not as satisfied.**
|
||||
|
||||
The length guard is the case that produced this rule: it compared a value with itself and could never
|
||||
fail. A property the tool cannot establish — the length of a site without disassembling it — is now a
|
||||
third state rather than a silent pass.
|
||||
|
||||
**Decision: fixtures are generated, not committed.**
|
||||
|
||||
A committed sample is a licensing and provenance problem, and a downloaded one makes the suite depend
|
||||
on the network and on a target that changes. A generated fixture is deterministic, carries the awkward
|
||||
cases deliberately, and is code rather than an artifact.
|
||||
|
||||
## The gates
|
||||
|
||||
**Decision: six structural gates at the repository root, and CI runs all of them.**
|
||||
|
||||
Consistency has a natural counter-pressure — a broken path fails loudly. Bloat and drift do not, which
|
||||
is why there is a budget gate and a routing gate rather than only link checking. Every gate exists
|
||||
because a specific class of change went unnoticed without it.
|
||||
|
||||
**Decision: the leak scanner owns its rules, and the repository gate delegates to it.**
|
||||
|
||||
One place to argue with an exemption list. A second implementation would diverge, and the diverging
|
||||
one would be the one that ran.
|
||||
|
||||
**Decision: the claim that a document makes about a command is machine-checked against that script's
|
||||
own argument table.**
|
||||
|
||||
A flag that no longer exists is drift no path check can see: the file resolves, the line number is
|
||||
right, and the command fails at the moment someone follows the page instead of the tool.
|
||||
|
||||
## The hardware layer
|
||||
|
||||
**Decision: the hardware routes are probed per host and degraded honestly, rather than declared
|
||||
supported.**
|
||||
|
||||
This is the skill's differentiator and its least portable part. A CPU reporting the feature says
|
||||
nothing about whether a capture path is reachable, and a hypervisor present on the analysis host
|
||||
changes what a debugger and a hardware breakpoint can do. A plan that assumes otherwise fails at the
|
||||
first step and gets attributed to the tooling. The probe exists so the answer is a measurement.
|
||||
|
||||
**Decision: bits whose meanings are not verified are printed raw.**
|
||||
|
||||
Reading a capability out of a recollection is how a wrong plan becomes an authoritative-looking one.
|
||||
There is no loss in printing the register values and letting the reader consult the processor
|
||||
specification for the family in front of them.
|
||||
|
||||
## Publishing
|
||||
|
||||
**Decision: the desensitization rules state what must be removed *and* what must stay.**
|
||||
|
||||
A one-sided rule trains people to redact tool names and format names, which destroys the reusable part
|
||||
of the material while removing none of the target's identity. A scanner that flags those gets switched
|
||||
off, and then the real findings go with it.
|
||||
|
||||
**Decision: an entry-point document is treated as a prompt surface.**
|
||||
|
||||
A README, a case note, a screenshot filename — these carry target identity as easily as body text, and
|
||||
they are the part most often forgotten because they were written before the material was.
|
||||
@@ -0,0 +1,110 @@
|
||||
# Tool Verdicts
|
||||
|
||||
What was verified about each tool this skill depends on or recommends, **and what is only
|
||||
documentation**. Two different questions live here and they are kept apart: "is the tool present on
|
||||
the machine that did this work" and "is what the skill says about the tool true".
|
||||
|
||||
A tool being absent from the development host is not a defect in the skill. It is the reason a
|
||||
route's grade is `inferred` rather than `observed`, and it is recorded so a reader knows which rows
|
||||
are plan-shaped and which are run-shaped.
|
||||
|
||||
## Local inventory
|
||||
|
||||
Measured on the development host with a path lookup and a version call. Windows 11 build 26100,
|
||||
Python 3.14.
|
||||
|
||||
| Tool | On the host | Used for |
|
||||
|---|---|---|
|
||||
| Python 3.14 | present | every script |
|
||||
| `git` | present | repository work |
|
||||
| `gcc`, `objdump`, `strings`, `gdb` (MinGW) | present | building small C helpers, section and symbol dumps |
|
||||
| `go` | present | reading a Go binary's symbols and code |
|
||||
| `dotnet` | present | managed assemblies |
|
||||
| `java`, `javac` | present | tools distributed as archives |
|
||||
| `node`, `npm` | present | the `skills` CLI, and scriptable tooling |
|
||||
| `frida` | present | runtime instrumentation |
|
||||
| `wpr`, `logman`, `tracerpt`, `bcdedit` | present | OS-level event tracing and boot configuration |
|
||||
| `nasm` | present | assembling small stubs |
|
||||
| `curl`, `tar` | present | fetching and unpacking archives |
|
||||
| A disassembler (IDA, Ghidra, Binary Ninja, Cutter) | **absent** | reading code; several are GUI-only |
|
||||
| `x64dbg`, `WinDbg`, `cdb` | **absent** | interactive and command-line debugging |
|
||||
| `dumpbin`, `link`/`editbin`, `signtool` (MSVC) | **absent** | header inspection and signing |
|
||||
| `ildasm`, `ilasm`, `corflags` | **absent** | CIL disassembly and assembly |
|
||||
| A packer/protector identifier | **absent** | shell and protector recognition |
|
||||
| `procdump` | **absent** | process dumping |
|
||||
| `tshark` | **absent** | capture reading |
|
||||
| `cargo`, `rustc` | **absent** | building Rust tooling |
|
||||
| `cl`, `msbuild` | **absent** | building from the MSVC toolchain |
|
||||
|
||||
The scripts treat absence as a report, not a failure: `scripts/hwprobe.py --tools` prints what is
|
||||
present and names what is not, and `EXEREV_TOOLS` lets a toolchain that lives outside `PATH` be found.
|
||||
|
||||
## What is verified about each mechanism
|
||||
|
||||
Three columns: what the skill says, how strongly it is supported, and what would raise it.
|
||||
|
||||
| Mechanism | Support | What would raise it |
|
||||
|---|---|---|
|
||||
| PE structure, flag values, directory semantics, checksum | `observed` against the published format specification, and confirmed by the reader reproducing the declared checksum on real images | nothing further needed for the fields in use |
|
||||
| Import, export, resource, TLS, debug and relocation parsing | `observed` against fixtures that declare their own expected values, plus plausible results on platform images | a second, independent parser agreeing on a larger corpus |
|
||||
| Byte patching, checksum recomputation, patch verification | `observed` by running the pipeline end to end on a copy and re-reading the written bytes | nothing further for the described behaviour |
|
||||
| Pattern location and cross-build comparison | `observed` on generated fixtures, including the ambiguous and zero-match cases | a real multi-version pair to exercise migration |
|
||||
| Managed-assembly structure and tooling | `inferred` from the platform's own published format documentation and the tools' own documentation | a managed sample and a run |
|
||||
| Installer and package formats | `inferred` from each project's own documentation | extraction performed on a sample of each container type |
|
||||
| Debugger breakpoint families and their costs | `inferred` from the debugger's own documentation | a debugger installed, and a session recorded |
|
||||
| Injection and hooking mechanics | `inferred` from the platform API documentation | a run, on a sample, with a control |
|
||||
| Runtime gate verdict by paired observation | `observed` end to end on a real gated feature of a patched snapshot target: the code section was located by content match, the six patched bytes were read back live from the mapped code before any verdict was trusted, the shared decision function was entered exactly once, returned the true-object pointer, and the refusal display routine was never entered | a second target whose gate shape differs, to exercise the other two verdict states against reality |
|
||||
| Dump semantics and repair | `inferred` from the platform's dump documentation | a dump repaired and loaded |
|
||||
| Hardware breakpoints | `inferred` from the architecture as documented; the **count** of debug registers is the only architectural fact stated | a debugger, and a session on a host whose probe reports the route available |
|
||||
| Instruction tracing capability and decoding | `unverified` here: the CPU reports the feature and no capture was taken | a capture path, then a decoder, then an address-to-symbol pass |
|
||||
| OS event tracing | `inferred`; the tools are present and no session was recorded | a recorded trace of a known operation |
|
||||
| Kernel debugging and driver loading | `inferred` from platform documentation | a second machine, or a boot configuration change, plus a driver to load |
|
||||
| Hypervisor-assisted observation | `unverified`; documented as a route with its requirement chain | a driver, signing, and a host that is not already nested |
|
||||
|
||||
## Facts worth stating because common belief differs
|
||||
|
||||
Each of these was checked against a primary source, and each contradicts a thing that is widely
|
||||
repeated. They are recorded here because a reader acting on the common version will build the wrong
|
||||
plan.
|
||||
|
||||
- **A strong name is not anti-tamper.** It provides identity, and on current runtimes the signature is
|
||||
not validated at load time. Work planned around defeating it is planned against a mechanism that is
|
||||
not doing the work.
|
||||
- **A managed assembly can be compiled to something with no readable intermediate code.** A
|
||||
ready-to-run image can still have its methods replaced by the runtime's compiler, so "precompiled"
|
||||
does not mean locked; a native-compiled deployment is the one that genuinely removes the readable
|
||||
layer.
|
||||
- **`-A` on a disassembler's command line is not "run this script".** It suppresses dialogs; the script
|
||||
is a separate switch, and the two are documented as being used together.
|
||||
- **The Rich Header is not in the format specification.** The published document does not describe it
|
||||
at all, so a parser for it is reading an undocumented structure and must say so rather than implying
|
||||
a normative source.
|
||||
- **A full dump is not always the most complete dump.** The largest minidump configuration can carry
|
||||
more than a nominal "full" one, and one of the flags commonly believed to mean "everything" is
|
||||
documented as not supported for user-mode dumps.
|
||||
- **A protected process cannot be recorded by the record-and-replay debugger**, and its replay is
|
||||
read-only. A recording is evidence about the run that was recorded, not about the binary.
|
||||
- **A page-guard breakpoint fires once.** It is an alarm that the page was touched, not a watchpoint,
|
||||
and treating it as one loses every access after the first.
|
||||
- **A copy-on-write image page is still reported as an image**, not as private memory. Distinguishing
|
||||
it needs the working-set information rather than the region query.
|
||||
- **An administrative installation of a package removes its digital signature.** The extraction route
|
||||
and the signing state are not independent.
|
||||
- **A certificate in the image is a statement about the image digest**, which is why editing the file
|
||||
while preserving the digest is not a thing that happens.
|
||||
|
||||
## What is deliberately not asserted
|
||||
|
||||
These came up during the research and were left out of the documentation rather than stated from
|
||||
partial evidence:
|
||||
|
||||
- The exact byte ranges excluded from an Authenticode digest. The page that would have said so was not
|
||||
reachable; the consequence of editing is documented, the exclusion list is not.
|
||||
- The deterministic-build behaviour of the linker's timestamp field. Candidate specification pages
|
||||
were not reachable, so build identity is described through the file hash and product version, with
|
||||
the format's own caveat about the timestamp.
|
||||
- Byte values of any standard cryptographic substitution table. The standard is named and the reader
|
||||
is told to compare against it rather than against a recollection.
|
||||
- Information classes for the undocumented memory-query call. The family is named and the reader is
|
||||
pointed at the target's own call sites.
|
||||
- The detailed control layout of the debug registers. Only the architectural count is stated.
|
||||
Reference in New Issue
Block a user