Anti-Tamper Probe Integration
This document explains how anti-tamper probes work today in Mutant, including the exact enablement gates and how runner enforcement differs from builtin diagnostic calls.
This document explains how anti-tamper probes work today in Mutant, including the exact enablement gates and how runner enforcement differs from builtin diagnostic calls.
1. Architecture#
Core files:
security/antitamper_probe.go(engine + enablement gate)security/antitamper_routing.go(probe dispatch)security/antitamper_detectors.go(cross-platform heuristics)security/antitamper_windows.go(windows process-protection heuristics)runner/runner.go(enforcement)builtin/security_status.go(diagnostic exposure)
Telemetry events used by this subsystem:
anti_tamper_probe_invokedanti_tamper_probe_errorprocess_protection_detected
2. Enablement Model (Important)#
Both gates are on, always. Neither is configurable. They used to be environment variables; Mutant takes no configuration from the environment, and these are not per-run decisions. See CONFIGURATION_POLICY.md.
- Master probe gate:
antiTamperProbeEnabled(security/antitamper_probe.go), a package-leveltrue.SetAntiTamperProbeEnabledForTestingexists only so tests can turn probing off. - Runner process-protection gate:
isProcessProtectionEnabled(runner/runner.go), which returnstrue.
Behavior:
RunAntiTamperProbereturnsenabled=trueand runs the requested probes. It returnsenabled=falseonly inside a test that has flipped gate #1.- Gate #2 governs runner enforcement only.
enforceProcessProtectionconsults it, then runs the five process-protection probes. - What still varies per run is the response, not the probing: a signal at
confidence
>= 80(processProtectionTerminateConfidence) terminates in secure mode and warns under--compat/--dev. See ANTITAMPER_PROBE_ENABLEMENT_LLD.md.
3. Probe Output Shape#
Each probe returns one AntiTamperSignal with:
namemeasureddetectedconfidencedetail
Interpretation:
measuredsays whether the probe looked. A probe that cannot run on the host, or whose own check failed, answersmeasured=false,detected=false, confidence0, and adetailsaying why. Itsdetected=falseis then not a finding: "not detected" and "not looked for" are different answers, and before 2.6.0 they looked the same.detectedis evidence for that probe only.confidenceis a per-probe confidence score, not a global verdict.- policy action is decided by caller logic (runner or builtin consumer).
4. Implemented Probes (Current)#
timingfrida_ptracesleep_driftiat_gotsyscall_tabletrampolineprocess_injectionmodule_integritymemory_page_anomaly
Six names were listed here until 2.6.0 and are no longer probes; asking for
one returns the unknown probe signal.
acpi_pcire-reported the sandbox detector's verdict, whichsandbox_statusreports in its own fields, and read no ACPI table or PCI device.cpuid_hypervisorre-reported the same verdict and read no CPUID of its own, so on a host whose CPUID reports a hypervisor it could answer "no hypervisor signal". The CPUID reading the sandbox detector takes is one ofsandbox_status's indicators.hardware_breakpointandsyscallre-reported the debugger detector's verdict -- a parent process namedgdbcame out as a hardware breakpoint at confidence 70 and as a syscall finding at 80 -- and neither read a debug register or made any check of its own. The checks the debugger detector ran aredebug_status's indicators (section 5).gpu_featurewas a placeholder that answered "not implemented yet".ld_preloadanswered "env-based preload checks disabled". It could not have seen anything: a release is built withCGO_ENABLED=0and statically linked, so the dynamic loader thatLD_PRELOADworks through never runs.
One more was renamed: sleep_drift was called rdtsc_drift, which named an
RDTSC check it never made. It times short sleeps on the OS clock and reports,
in sleep_ms and drift_ms, how far their total ran from what they asked for.
5. Runner Enforcement vs Builtin Diagnostics#
Runner enforcement probe set (hardcoded in runner/runner.go):
process_injectiontrampolineiat_gotmodule_integritymemory_page_anomaly
Runner threshold:
- any signal with
detected=trueandconfidence >= 80triggersprocess_protection_detectedpolicy flow.
All five read Windows process structures, so they run on Windows only. On Linux
and macOS every one answers measured=false: process protection looks at
nothing there, the stage passes, and the run says so once on stderr --
[security] process protection measured nothing on linux: ....
Builtin diagnostics (builtin/security_status.go) use broader probe sets for
visibility and troubleshooting. This is expected and independent of runner
enforcement scope.
debug_status reports one reading of the debugger detector beside its probes:
detectedis the platform detector's verdict, by its own rule. The Windows detector, for one, wants two heuristics before it reports a debugger on heuristics alone.indicatorsare the checks that fired, assecurity_diagnosticsnames them (windows:is_debugger_present,linux:parent_debugger_process, ...).confidencefollows from which checks those are. A direct check reads the operating system's own record of a debugger:windows:is_debugger_present,windows:check_remote_debugger_present,windows:process_debug_port,windows:process_debug_object_handle,windows:system_debug_control_query_version,linux:tracer_pidanddarwin:p_traced. When one fired,confidenceis 100. Every other check -- a debugger's name on the parent process, a debugger's environment variable, OutputDebugString timing, a debugger's DLL loaded -- is a heuristic, and heuristics alone give 35 for one and 70 for two or more. Nothing fired is 0.
Before 2.6.0 confidence was 100 for any detection and indicators was
always empty, so a parent process that happened to be named gdb read exactly
as a debugger the kernel says is attached. Because detected is the
detector's rule and confidence is this one, a reading can be detected below
100, or not detected above 0: one heuristic on Windows reads
detected: false with confidence: 35.
6. Platform Notes#
Windows:
process_injectionuses tasklist marker heuristics. It reads no environment variable: the five it read before 2.6.0 configure the .NET runtime, a Java VM and symbol handlers such as dbghelp's, none of which runs in Mutant's process (see PROCESS_INJECTION_DETECTION_LLD.md, section 4.1).trampolinechecks selected API prologues for common hook patterns.iat_gotchecks sensitive export memory ownership against expected module.module_integritychecks suspicious loaded module markers.memory_page_anomalychecks for RWX pages on sensitive API addresses.
Linux:
frida_ptracechecks FRIDA env markers and/proc/self/statustracer PID.
Linux, macOS and anything else:
iat_got,syscall_table,trampoline,process_injection,module_integrityandmemory_page_anomalyanswermeasured=false, withnot measured on <os>: this probe reads Windows process structures.
7. Student Notes#
- Probe enablement and enforcement are not the same thing.
- Builtin probe output is diagnostic; runner probe output can become policy action.
- Always read
detailbefore acting on a single signal.