Skip to content

Latest commit

 

History

History
667 lines (528 loc) · 27.8 KB

toolchain.md

File metadata and controls

667 lines (528 loc) · 27.8 KB

MalwarePinAnalyzer toolchain

The phase-one build is intentionally pinned to the toolchain installed with this Pin kit. Do not mix headers, import libraries, or runtimes from another kit.

Component Version
Intel Pin 4.3, kit build 99850 (ce5652921)
Target Windows 10/11 x64 and WOW64 x86, Pin JIT mode
Visual Studio Professional 2026 18.7.3
Toolset ClangCL x64/Win32, shared C++17 sources
Windows SDK 10.0.26100.0
Runtime restrictions RTTI off; no C++ exception dependency

The x86 artifact has no x64 component dependency and is also intended for native Windows 10 x86 deployment. That native-x86 runtime has not been tested in the current environment; the automated x86 acceptance below runs under WOW64 on x64 Windows.

Build

Run from source\tools\MyPinTool:

& 'D:\Microsoft Visual Studio\18\Professional\MSBuild\Current\Bin\MSBuild.exe' `
  '.\MalwarePinAnalyzer-clang.vcxproj' /t:Build `
  /p:Configuration=Release /p:Platform=x64 /nologo
& 'D:\Microsoft Visual Studio\18\Professional\MSBuild\Current\Bin\MSBuild.exe' `
  '.\MalwarePinAnalyzer-clang.vcxproj' /t:Build `
  /p:Configuration=Release /p:Platform=Win32 /nologo

The outputs are:

  • x64\Release\MalwarePinAnalyzer.dll for x64 targets;
  • x86\Release\MalwarePinAnalyzer.dll for x86 targets.

Build the harmless integration sample for both platforms with the same commands, replacing the project path with tests\native\PhaseOneSample.vcxproj. Its project references build the two test DLLs in the matching architecture. x64 samples are written to tests\bin; x86 samples are written to tests\bin\x86.

Run

New-Item -ItemType Directory -Force '.\output'
& 'E:\pin\pin.exe' -t '.\x64\Release\MalwarePinAnalyzer.dll' `
  -mode observe -o '.\output\events.bin' -max_buffer_size 65536 `
  -- '.\sample.exe'

For an x86 target on x64 Windows, use the 32-bit Pin launcher and Pintool:

& 'E:\pin\pin32.exe' -t '.\x86\Release\MalwarePinAnalyzer.dll' `
  -mode observe -o '.\output\events-x86.bin' -max_buffer_size 65536 `
  -- '.\sample-x86.exe'

The target executable and Pintool must have the same architecture. The x64 log file header contains pointer_width=8; the x86 header contains pointer_width=4. Both remain protocol v1 and encode every address, pointer, handle, and RVA as a zero-extended little-endian UINT64.

neutralize currently hides positive results from IsDebuggerPresent, CheckRemoteDebuggerPresent, and the supported NtQueryInformationProcess debug information classes. Every modified fact retains original_value, effective_value, and action=neutralized in the anti-analysis report. force changes only the approved debugger-presence queries. Select the forced state explicitly with -force_anti_debug absent|present; every such event retains its original and effective value and uses action=forced. Other PEB/TEB, CPU, exception, environment, and integrity observations remain evidence-only. The startup diagnostic reports inactive=none for the declared first-version switches.

Enable the opt-in process-wide virtual clock only in neutralize mode:

& 'E:\pin\pin.exe' -t '.\x64\Release\MalwarePinAnalyzer.dll' `
  -mode neutralize -virtual_time 1 -virtual_time_rate 10 `
  -anti_analysis 1 -anti_instructions 1 `
  -o '.\output\virtual-time-x64.bin' `
  -- '.\sample.exe'

virtual_time_rate is an integer percentage from 1 through 100. The default is 10, but it has no effect while virtual_time remains 0. QPC, GetTickCount, GetTickCount64, NtQuerySystemTime, RDTSC, and RDTSCP retain per-source real baselines and share process-wide monotonic state. Reports preserve the raw and effective values for every modification.

Decode and test

Use Python 3.10 or newer:

python -m analyzer.cli decode '.\output\events.bin' `
  --output '.\output\events.jsonl'
python -m analyzer.cli api-inventory '.\output\events.bin' `
  --output '.\output\api-inventory.json'
python -m unittest discover -s tests -v

API tracking defaults to -api_tracking auto: named API boundaries reached from the main executable, configured sample DLLs, or tracked private executable memory are recorded without a hard-coded allowlist. Compile the user-facing JSON capture metadata before a direct Pintool run:

python -m analyzer.cli compile-api-config '.\rules\api-capture.example.json' `
  --output '.\output\api-capture.tsv'
& 'E:\pin\pin.exe' -t '.\x64\Release\MalwarePinAnalyzer.dll' `
  -api_tracking auto -api_capture_config '.\output\api-capture.tsv' `
  -o '.\output\events.bin' -- '.\sample.exe'

The deployment mpa.ps1 -ApiConfig <json> path performs this compilation automatically and validates the JSON before the target starts.

Build the phase-6A cross-domain report and compare two runs with stable semantic fingerprints:

python -m analyzer.cli behavior-report '.\output\events.bin' `
  --output '.\output\behavior-report.json'
python -m analyzer.cli run-diff '.\output\observe.bin' `
  '.\output\neutralize.bin' --output '.\output\run-diff.json'

The diff intentionally excludes PID, thread/call IDs, event sequence numbers, absolute code addresses, and module load bases. It retains normalized object names, endpoints, action, and whether original/effective values differ.

Phase 6A network chains

The API collector records socket, connect, send, recv, closesocket, getaddrinfo/freeaddrinfo, and the WinHTTP open/connect/request/send/read/ close lifecycle. Buffer contents are not copied by default; reports retain names, endpoints, requested/actual sizes when reliable, status, and handle lineage. Use -capture_buffers 1 -max_capture_size N to copy bounded input and output bytes as hexadecimal. N must be between 1 and 16384, short reads and truncation are explicit, and invalid application pointers never get directly dereferenced. Exports whose IPOINT_AFTER result is unreliable on supported Windows builds are deliberately marked entry_only; DNS results are safely read at freeaddrinfo, and WinHTTP request handles are recovered from later calls.

The harmless integration sample enables its loopback-only DNS/socket/WinHTTP exercise with MPA_EXERCISE_NETWORK_CHAIN=1. Real targets need no environment variable—the variable controls only the test sample behavior.

tests.test_pin_integration.PinX64IntegrationTests uses pin.exe and PinX86IntegrationTests uses pin32.exe. Override their locations with PIN_EXE_X64 and PIN_EXE_X86 when the Pin kit is installed elsewhere.

For native Windows 10 x86, copy the complete Pin kit, the x86\Release\MalwarePinAnalyzer.dll artifact, and an x86 target to that machine, then run the same pin32.exe command shown above. Do not use the x64 Pintool or x64 sample there. This deployment path remains pending real machine/VM confirmation.

The physical file order reflects per-thread batch flushes. Sort decoded events by global_sequence when reconstructing a cross-thread timeline.

Phase 6B hollowing and IOC reports

The collector records CreateProcessW, NtUnmapViewOfSection, SetThreadContext, ResumeThread, and named CreateMutexW objects. Process handles returned by CreateProcessW join the existing remote allocation/write events, while primary-thread handles join context and resume events. The offline detector requires CREATE_SUSPENDED, matching handle generations, and the strict unmap/allocate/write/context/resume order. Because the ntdll unmap wrapper is entry-only, the report calls the result an inferred candidate and does not claim that unmapping succeeded.

python -m analyzer.cli hollowing-report '.\output\events.bin' `
  --output '.\output\hollowing-report.json'
python -m analyzer.cli ioc-report '.\output\events.bin' `
  --dump-dir '.\output\dumps' --output '.\output\ioc-report.json'

The IOC report includes file/registry/network indicators, process command lines, mutexes, service and scheduled-task registry names, hashes of private memory dumps and captured API buffers, and fingerprints of complete PEM certificate/public-key blocks found in opted-in buffers. Every artifact keeps its event or file provenance; persistence and hollowing labels remain marked as inferred.

Phase 2A API tracing

The Observe collector records LoadLibraryA/W, GetProcAddress, VirtualAlloc, VirtualProtect, VirtualFree, CreateFileW, and connect. After decoding, list API activity with:

Get-Content '.\output\events.jsonl' | ForEach-Object {
  $_ | ConvertFrom-Json
} | Where-Object type -in @('api_enter', 'api_exit') |
  Select-Object type, call_id, thread_id, fields

Many Kernel32 exports forward into KernelBase. The collector instruments both layers to retain public entries and capture returns from real implementations. LoadLibraryW may remain entry-only when no usable IPOINT_AFTER exists.

Phase 2B behavior timeline

File and registry handle lifetimes are correlated in the collector. Generate a paired timeline directly from a binary log with:

python -m analyzer.cli timeline '.\output\events.bin' `
  --output '.\output\timeline.json'

The timeline is ordered by global_sequence; complete calls merge entry and exit fields while entry-only calls remain explicitly marked.

API call tree

Generate the API-only tree in both machine-readable and terminal formats:

python -m analyzer.cli call-tree '.\output\events.bin' `
  --json-output '.\output\api-call-tree.json' `
  --text-output '.\output\api-call-tree.txt'

Trees are isolated by thread and use captured parent_call_id values; missing or invalid parents remain visible as orphaned roots.

Full dynamic function call tree

Full CALL/RET tracking is opt-in because it adds substantial runtime overhead and can make the binary log much larger. Enable it and explicitly identify one or more sample DLLs with a quoted, comma-separated list:

& 'E:\pin\pin.exe' -t '.\x64\Release\MalwarePinAnalyzer.dll' `
  -full_call_tree 1 `
  -sample_module 'sample_a.dll, sample_b.dll' `
  -o '.\output\events.bin' `
  -- '.\sample.exe'

-full_call_tree defaults to 0. A sample selector can be a DLL file name or an absolute path; matching is case-insensitive, absolute paths match exactly, and ambiguous or unloaded selectors produce diagnostic events. The main image and selected DLLs are expanded recursively. Exact named entries in trusted Windows system directories are recorded as API leaves, while unnamed system targets continue to expand. Unselected third-party DLLs are external leaves. Private executable code is tracked only after an active sample call chain enters it.

-trace_main 0 excludes the main executable as a recursive trace root, while -trace_sample_dll 0 excludes explicitly selected DLLs. A call crossing into an excluded root remains a visible boundary when its caller is active, but its children are suppressed; a callback into an enabled root resumes collection. The same two switches scope sample instruction-level anti-analysis and self-integrity instrumentation. API lifecycle collection remains process-wide.

Calls made internally by an API leaf are suppressed. If that API calls back into the main image or a selected sample DLL, the callback and its sample children remain visible, and suppression resumes when the callback returns.

Generate the full tree as JSON and UTF-8 BOM text:

python -m analyzer.cli full-call-tree '.\output\events.bin' `
  --json-output '.\output\full-call-tree.json' `
  --text-output '.\output\full-call-tree.txt'

The full tree is independent of the API tree. It retains absolute addresses, module IDs/RVAs, names when available, API-leaf/callback flags, and explicit returned, unwound, or incomplete status.

Phase 4A local memory lifecycle

Local VirtualAlloc, VirtualProtect, and VirtualFree calls are correlated with private execution. The default light mode records allocation, protection, execution, and free events without instrumenting every write. Enable registered-region write tracking explicitly when required:

& 'E:\pin\pin.exe' -t '.\x64\Release\MalwarePinAnalyzer.dll' `
  -memory_lifecycle 1 -memory_write_tracking deep `
  -trace_private_exec 1 -dump_private_exec 1 `
  -dump_dir '.\output\dumps' -max_dump_size 16777216 `
  -o '.\output\events.bin' -- '.\sample.exe'

Use pin32.exe and the x86 Pintool for a 32-bit sample. Dumping is off by default. deep adds a runtime check to application memory-write instructions, so use light when only allocation/protection/execution transitions are needed.

Generate the two offline memory views with:

python -m analyzer.cli memory-timeline '.\output\events.bin' `
  --output '.\output\memory-timeline.json'
python -m analyzer.cli unpack-report '.\output\events.bin' `
  --dump-dir '.\output\dumps' `
  --output '.\output\unpack-report.json'

The unpack report labels only evidence-backed local candidates. It calculates Dump SHA-256, Shannon entropy, and bounded PE structure validity offline. Native NT memory APIs, mapped sections, and syscall-level correlation remain phase 4B2 work.

Phase 4B1 Win32 remote-memory observation

Remote memory and remote thread observation are enabled by default. Remote thread tracking requires remote memory tracking:

& 'E:\pin\pin.exe' -t '.\x64\Release\MalwarePinAnalyzer.dll' `
  -remote_memory 1 -remote_thread 1 `
  -o '.\output\remote-events-x64.bin' -- '.\tests\bin\phase_one_sample.exe'

Use pin32.exe, the x86 Pintool, and the x86 sample for WOW64. The collector observes OpenProcess, VirtualAllocEx, WriteProcessMemory, VirtualProtectEx, CreateRemoteThread, and VirtualFreeEx. It never reads a true remote destination using PIN_SafeCopy; only local input/output pointers are safely probed.

python -m analyzer.cli remote-memory-timeline '.\output\remote-events-x64.bin' `
  --output '.\output\remote-memory-timeline-x64.json'
python -m analyzer.cli injection-report '.\output\remote-events-x64.bin' `
  --output '.\output\injection-report-x64.json'

The integration test targets only its own process using a six-byte function that returns a constant; it never accesses or modifies another process.

Phase 4B2 NT Native memory, sections, and syscalls

The default memory syscall profile records the supported Native memory, section, and thread syscalls while retaining unresolved direct-syscall facts:

& 'E:\pin\pin.exe' -t '.\x64\Release\MalwarePinAnalyzer.dll' `
  -native_memory 1 -section_tracking 1 -syscall_tracking memory `
  -o '.\output\b2-events-x64.bin' -- '.\tests\bin\phase_one_sample.exe'

& 'E:\pin\pin32.exe' -t '.\x86\Release\MalwarePinAnalyzer.dll' `
  -native_memory 1 -section_tracking 1 -syscall_tracking memory `
  -o '.\output\b2-events-x86.bin' -- '.\tests\bin\x86\phase_one_sample.exe'

Use -syscall_tracking all only when a complete syscall fact stream is required; it increases log volume substantially. off disables syscall and Native semantic events. Syscall numbers are learned from the ntdll stubs used by the current process and are never compiled into the Pintool.

python -m analyzer.cli syscall-timeline '.\output\b2-events-x64.bin' `
  --output '.\output\syscall-timeline-x64.json'
python -m analyzer.cli section-timeline '.\output\b2-events-x64.bin' `
  --output '.\output\section-timeline-x64.json'
python -m analyzer.cli injection-report '.\output\b2-events-x64.bin' `
  --output '.\output\injection-report-b2-x64.json'

Replace x64 with x86 in the three analysis commands to process the WOW64 log. decode remains the lossless event-stream export and writes JSONL; the syscall timeline, section timeline, and injection report are aggregate documents and therefore write JSON.

API call IDs and syscall call IDs share a 64-bit field in semantic events but use disjoint namespaces. Syscall call IDs have bit 63 set, so offline tools can distinguish Native syscall provenance from Win32 wrapper provenance without guessing from numeric ordering.

The x64 integration sample also copies the current operating system's NtAllocateVirtualMemory stub and performs one self-allocation. This validates direct-syscall attribution without embedding a Windows-build-specific syscall number. The WOW64 test validates isolated runtime mapping through its native ntdll transition path and does not copy that platform-specific transition.

Phase 5A/5B anti-analysis observation

The PEB/TEB, CPUID, exception, environment, and self-integrity sources remain observation-only. Phase 5C adds narrowly approved anti-debug and virtual-time neutralization. Force mode can explicitly make the approved debugger queries report present or absent:

& 'E:\pin\pin.exe' -t '.\x64\Release\MalwarePinAnalyzer.dll' `
  -mode force -force_anti_debug present `
  -o '.\output\force-debug-present.bin' -- '.\sample.exe'
& 'E:\pin\pin.exe' -t '.\x64\Release\MalwarePinAnalyzer.dll' `
  -anti_analysis 1 -anti_instructions 1 -exception_tracking 1 `
  -peb_teb_tracking 1 `
  -self_integrity_tracking 1 -self_integrity_threshold 64 `
  -o '.\output\anti-analysis-events-x64.bin' -- `
  '.\tests\bin\phase_one_sample.exe'

python -m analyzer.cli anti-analysis-report `
  '.\output\anti-analysis-events-x64.bin' `
  --output '.\output\anti-analysis-report-x64.json'

Use pin32.exe, the x86 DLL/sample paths, and x86 output names for WOW64. Native ntdll syscall stubs are explicitly reported as entry_only because Pin routine return instrumentation is not reliable for those stubs. The report distinguishes entry facts from exit facts with result_phase and result_available; it never substitutes a fabricated return value.

Self-integrity tracking is disabled by default because it inserts a callback for sample memory reads. When enabled, it only treats reads targeting selected sample executable sections as candidates, aggregates them in fixed per-thread slots, and emits at the configured threshold. The report includes target address/page, cumulative read count/bytes, and nearby compare/conditional- branch evidence.

Phase 7 child processes and process-tree output

Child following is opt-in. Every instrumented process writes a separate file; when the configured path has no {pid} placeholder, the tool inserts the PID before the extension automatically:

& 'E:\pin\pin.exe' -follow_execv `
  -t '.\x64\Release\MalwarePinAnalyzer.dll' `
  -follow_child 1 -o '.\output\events.bin' -- '.\sample.exe'

For an x64 parent that can create WOW64/x86 children, provide both tools so Pin can switch architecture at exec time:

& 'E:\pin\pin.exe' -follow_execv `
  -t64 '.\x64\Release\MalwarePinAnalyzer.dll' `
  -t '.\x86\Release\MalwarePinAnalyzer.dll' `
  -follow_child 1 -o '.\output\events-{pid}.bin' -- '.\sample.exe'

Merge all per-process files into one JSON process tree and advisory event timeline:

python -m analyzer.cli process-tree .\output\events-*.bin `
  --output '.\output\process-tree.json'

The report validates CREATE/START linkage, parent availability, duplicate PIDs, missing START/EXIT events, cycles, and architecture switches. A global_sequence is strict only within its owning process. Cross-process timestamp ordering is explicitly labeled timestamp_advisory and must not be treated as a total order.

Phase 8A bounded VMP ROI trace

Instruction tracing is disabled by default. Use a module selector with an RVA half-open interval to remain stable under ASLR:

& 'E:\pin\pin.exe' -t '.\x64\Release\MalwarePinAnalyzer.dll' `
  -vmp_trace 1 -vmp_trace_module 'sample.exe' `
  -vmp_trace_start 0x12000 -vmp_trace_end 0x18000 `
  -vmp_trace_thread 0 -vmp_trace_max_events 1000000 `
  -vmp_trace_memory 1 -o '.\output\vmp-events.bin' -- '.\sample.exe'

python -m analyzer.cli vmp-trace '.\output\vmp-events.bin' `
  --output '.\output\vmp-trace.json'

Without -vmp_trace_module, start/end are absolute process addresses and the start must be nonzero. A nonzero vmp_trace_thread is a Windows native thread ID filter. vmp_trace_memory=0 retains effective addresses and sizes but does not copy pre-execution values. The runtime cap is per process and suppresses only further VMP instruction events; normal module/thread/process exit events continue to be written.

Definitions (type_id=51) contain stable instruction IDs, address/module/RVA, bytes and disassembly. Runtime events (type_id=50) contain pre-execution register state, actual branch target/taken state, and at most two bounded memory operands. x86 and x64 share the format; register counts are 8 and 16. The pre-execution rflags/eflags value is included in every runtime event.

To select one or more named routines instead of manually supplying RVAs, pair the module selector with a comma-separated function list:

& 'E:\pin\pin.exe' -t '.\x64\Release\MalwarePinAnalyzer.dll' `
  -vmp_trace 1 -vmp_trace_module 'sample.exe' `
  -vmp_trace_function 'VmEntry,VmDispatch' `
  -vmp_trace_max_events 1000000 `
  -o '.\output\vmp-functions.bin' -- '.\sample.exe'

Names match either raw symbols or Pin's undecorated name-only form. Function ranges are tied to the matching image lifetime; unmatched selectors generate an explicit diagnostic. Address, private-exec, and function ROIs may be used together as a union.

Build evidence-scored dynamic blocks, dispatcher candidates, handler slices, and VIP/VSP register candidates with:

python -m analyzer.cli vmp-analyze '.\output\vmp-events.bin' `
  --output '.\output\vmp-analysis.json'

This report is heuristic and evidence-only. It does not hard-code VMProtect register roles. Rolling-key candidates require repeated nontrivial mutation evidence across handler boundaries, and the native rsp/esp register plus strong VIP/VSP candidates are explicitly downweighted.

Phase 8C/8D automatic ROI, state capture, and replay packages

Trace sample-owned private executable allocations without a manually known address range:

& 'E:\pin\pin.exe' -t '.\x64\Release\MalwarePinAnalyzer.dll' `
  -vmp_trace 1 -vmp_trace_private_exec 1 `
  -vmp_trace_max_events 1000000 `
  -o '.\output\vmp-private.bin' -- '.\sample.exe'

Alternatively, keep an instrumented module ROI dormant until a tracked API is entered, or bracket it with VMEntry/VMExit RVAs:

& 'E:\pin\pin.exe' -t '.\x64\Release\MalwarePinAnalyzer.dll' `
  -vmp_trace 1 -vmp_trace_module 'sample.exe' `
  -vmp_trace_start 0x12000 -vmp_trace_end 0x18000 `
  -vmp_trace_api 'VirtualAlloc,NtProtectVirtualMemory' `
  -vmp_trace_entry 0x12100 -vmp_trace_exit 0x17f00 `
  -o '.\output\vmp-triggered.bin' -- '.\sample.exe'

vmp_trace_api names must exist in the Pintool API rule table. API and Entry triggers are per thread. A VMExit instruction is included before that thread becomes dormant. The vmp-trace JSON report exposes ordered state_transitions alongside instruction definitions and runtime traces.

Export a selected handler slice as a bounded replay package:

python -m analyzer.cli vmp-replay-export '.\output\vmp-events.bin' `
  --slice-id 1 --output '.\output\vmp-replay-slice1.json'

The package includes observed instruction bytes/order, initial and final register snapshots, bounded memory inputs, and explicit missing-input lists.

Validate the supported scalar integer semantics against the next real observed register state:

python -m analyzer.cli vmp-replay-validate `
  '.\output\vmp-replay-slice1.json' `
  --output '.\output\vmp-replay-validation-slice1.json'

The built-in backend supports common register moves, integer arithmetic, bitwise operations, shifts/rotates, simple LEA, PUSH/POP, and captured memory reads. It separately reports validated, mismatch, unsupported, and unverifiable steps. Partial coverage is never labeled whole-handler semantic equivalence. The current x64/x86 validation fixtures achieve 69.2%/75% concrete coverage with zero mismatches; calls and out-of-ROI control flow remain explicit unsupported barriers.

vmp-analyze also scores rolling-key register candidates. A candidate requires at least eight handler boundaries, repeated non-small mutations, repeated transform-mnemonic evidence, and disassembly register mentions. Strong VIP/VSP candidates and the hardware stack pointer are penalized. Ordinary sample traces currently produce no rolling-key candidate; the synthetic VM-like contract produces the intended rbx candidate.

Install the optional official Triton backend into the project-local dependency directory (it does not modify the system Python):

python -m pip download --only-binary=:all: --dest .deps triton-library==1.0.0rc4
python -m pip install --no-index --target .deps\python `
  .deps\triton_library-1.0.0rc4-cp312-cp312-win_amd64.whl

Then run concrete validation and optionally symbolize selected registers and captured read memory:

python -m analyzer.cli vmp-triton-replay `
  '.\output\vmp-replay-slice1.json' `
  --symbolic-register rbx --symbolize-memory `
  --output '.\output\vmp-triton-slice1.json'

The backend uses each real observed pre-state, synchronizes captured memory, processes the actual instruction bytes with Triton, and compares written GPRs and stores with the next observed state/post-write event. It refuses uncaptured loads instead of accepting Triton's default zero memory. Current real x64/x86 slice reports validate 10/13 and 13/16 instructions (76.9%/81.25%) with zero mismatches; the remaining three calls per slice are explicit ROI barriers.

Release log validation and native x86 acceptance

Validate a completed event log independently of JSONL generation:

python -m analyzer.cli validate-log .\output\events.bin `
  --expected-pointer-width 4 --output .\output\validation.json

The command returns exit code 2 for a well-formed log that fails acceptance invariants, and exit code 1 for malformed input or I/O errors. It checks the required process/module/thread lifecycle events, the main-module event, header and event architecture agreement, unique nonzero global sequences, strictly increasing per-thread sequences, balanced thread start/exit events, and a zero target exit code.

On a native 32-bit Windows 10 acceptance host, run this from the extracted release package with a known-benign x86 target that exits normally:

.\native-x86-acceptance.ps1 -PinRoot C:\pin `
  -Target C:\tests\known-benign-x86.exe

The gate verifies that the OS is genuinely 32-bit and that pin32.exe, the Pintool, and the target all have PE Machine 0x014c. -AllowWow64 exists only to self-test the gate on a development host; without it, x64 Windows is rejected and cannot generate a formal native-x86 pass.

Unified user workflow

The release package now exposes mpa.ps1 as the recommended entry point. A standard collection and complete offline report bundle requires one command:

.\mpa.ps1 run -PinRoot E:\pin -Target C:\samples\sample.exe

The quick, standard, deep, and vmp presets trade collection overhead for depth. run performs collection and reporting, collect only records the binary evidence, report regenerates the complete report suite from an existing log, and verify runs strict log acceptance checks. The launcher auto-selects x64/x86 tools, supports target arguments, multiple sample modules and child following, refuses to mix new data with an existing raw log directory, and preserves nonzero target exit codes while still producing reports.

The underlying bundle command is also available directly:

python -m analyzer.cli report-suite .\output\events.bin `
  --output-dir .\output\reports --dump-dir .\output\dumps

It creates decoded JSONL, validation, behavior, IOC, API/full call trees, memory/unpack, remote-memory/injection/Hollowing, syscall/section, anti-analysis, VMP reports, a machine-readable summary, and a human-readable README.md. See USER_GUIDE.zh-CN.md in the release package for the complete Chinese workflow and report reference.