Polymorphic self-modifying code (SMC) as a runtime integrity mechanism on x86-64 — companion code for the talk presented at CECC 2022 (Smolenice, Slovakia) and the accompanying dissertation chapter by Gregory Morse and Tamás Kozsik.
This repository contains the full experimental implementation described in the paper "Tamper-Proofing with Self-Modifying Code". The core idea is that SMC executing on a von Neumann architecture introduces timing semantics that cannot be faithfully reproduced by translating the code to an equivalent non-SMC form. We exploit this property to build a checksum-based tamper-proofing primitive that can detect code modification at runtime.
Key contributions:
- Identification of an x86-64 instruction-encoding pattern that lets SMC swap between
ADC,SBB,XOR, andCMPby flipping 2 bits, enabling a polymorphic checksum with no conditional branches. - A static hand-written MASM variant (
TP.asm) and a fully dynamic variant generated at runtime via AsmJit. - Precise C reference implementations (
TPCompC,TPCompCLoop) that emulate the SMC behavior for correctness validation and performance comparison. - Timing benchmarks using every available Windows high-resolution timer (RDTSC, RDTSCP, Interrupt Time, Performance Counter, UTC, Tick Count, Multimedia).
- Hardware performance-counter measurement of
MACHINE_CLEARS.SMC(Intel) via the WinRing0 kernel driver to quantify pipeline-clear events.
Self-modifying code (SMC) creates unique challenges when it comes to timing semantics on modern hardware for accurate emulation by a maximally efficient non-SMC equivalent. We investigate the efficacy of using polymorphic SMC to produce a tamper-proofing mechanism that utilises high-accuracy timing measurements which cannot be reproduced by translating the code from the von Neumann architecture to a Harvard architecture. Our experimental results show that the dynamic SMC variant runs 90× faster than its non-SMC counterpart while the basic static variant runs ~8× slower — demonstrating a design space where, with the right unrolling strategy and page-offset selection, SMC achieves smaller size and higher performance than any equivalent non-SMC checksum.
This work was presented at:
Morse, G.; Kozsik, T. Tamper-Proofing with Self-Modifying Code. CECC 2022, Smolenice, Slovakia, 28 June 2022.
The full manuscript is currently unpublished — it is not yet available on arXiv or in any journal proceedings — and forms part of Gregory Morse's doctoral dissertation, which is under review at Eötvös Loránd University (ELTE).
| Requirement | Details |
|---|---|
| OS | Windows 10 / 11 (x64) |
| Compiler | Visual Studio 2019 or later (MSVC + MASM/ml64) |
| Architecture | x86-64 only |
| Privileges | Administrator required at runtime (kernel driver for MSR access) |
| Build system | CMake ≥ 3.21 + vcpkg (recommended) or .vcxproj directly |
| External library | AsmJit — auto-resolved by vcpkg, or manual build |
| Kernel driver | WinRing0 v1.2 — WinRing0x64.sys (digitally signed) |
Note: The performance-counter features (MSR read/write via
IOCTL_OLS_*) are accessed through the WinRing0 kernel driver. Without it the process skips counter setup gracefully but theMACHINE_CLEARS.SMCcolumns will show zero.
Recommended — vcpkg (automatic): vcpkg.json already declares the asmjit dependency.
When you configure with CMake (see Building), vcpkg installs AsmJit into
vcpkg_installed/ automatically — no separate clone or build step is needed.
Alternative — manual build with ASMJIT_DIR: If you prefer to build AsmJit yourself
and use the .vcxproj directly without vcpkg, set the ASMJIT_DIR environment variable to
the root of your AsmJit checkout (the directory containing the src subfolder), then build:
git clone https://github.com/asmjit/asmjit.git C:\path\to\asmjit
cd C:\path\to\asmjit
cmake -B build_vs2022_x64 -G "Visual Studio 17 2022" -A x64
cmake --build build_vs2022_x64 --config Debug
cmake --build build_vs2022_x64 --config Release# Set ASMJIT_DIR permanently for your user account
[System.Environment]::SetEnvironmentVariable("ASMJIT_DIR", "C:\path\to\asmjit", "User")SelfModTamper\PropertySheet.props reads $(ASMJIT_DIR) automatically; no manual editing
of the property sheet is required.
The digitally signed kernel driver WinRing0x64.sys is required to read hardware performance
counters (MACHINE_CLEARS.SMC). Without it the benchmark still runs but counter columns show zero.
Run the provided download script once before your first build:
powershell -ExecutionPolicy Bypass -File scripts\Get-WinRing0.ps1This downloads WinRing0_1_2_0.zip from openlibsys.org, extracts
WinRing0x64.sys into the repository root, and CMake copies it next to the executable
automatically. The file is excluded from version control via .gitignore.
If the download fails, download
WinRing0_1_2_0.zipmanually from http://openlibsys.org/, extractWinRing0x64.sys, and place it in the repository root before running cmake.
The executable registers and starts the driver as a Windows service on each run and stops/deletes it cleanly on exit.
| Tool | How to get |
|---|---|
| Visual Studio 2019+ | visualstudio.microsoft.com — Desktop Development with C++ workload |
| CMake ≥ 3.21 | Bundled with Visual Studio, or cmake.org |
| vcpkg | github.com/microsoft/vcpkg — clone and set VCPKG_ROOT |
Set VCPKG_ROOT permanently if you have not already:
[System.Environment]::SetEnvironmentVariable("VCPKG_ROOT", "C:\path\to\vcpkg", "User")AsmJit is installed automatically from vcpkg.json.
REM 1. Download the WinRing0 driver (one-time setup)
powershell -ExecutionPolicy Bypass -File scripts\Get-WinRing0.ps1
REM 2. Configure (installs AsmJit via vcpkg, generates Visual Studio project files in build\)
cmake --preset windows-x64
REM 3. Build
cmake --build --preset release
REM or for Debug:
cmake --build --preset debugOutput: build\Release\SelfModTamper.exe / build\Debug\SelfModTamper.exe
The preset uses the Visual Studio 17 2022 generator. If you only have Visual Studio 2026 build tools, edit the
"generator"field inCMakePresets.jsonto"Visual Studio 18 2026".
Visual Studio 2022 and later detect CMakeLists.txt and CMakePresets.json when you use
File → Open → Folder.
- Run
scripts\Get-WinRing0.ps1once (see above). - File → Open → Folder — select the repository root.
- Visual Studio configures CMake and restores vcpkg packages automatically.
- Select the x64-Release or x64-Debug target and build.
Ensure
VCPKG_ROOTis set in your user environment before opening Visual Studio.
The original SelfModTamper.sln / .vcxproj remain for users who prefer the MSBuild
workflow. AsmJit paths are resolved via PropertySheet.props.
Providing AsmJit via vcpkg (required):
Either run vcpkg integrate install once (applies vcpkg to all VS projects on this machine):
vcpkg integrate installOr set VCPKG_ROOT and restart Visual Studio — Directory.Build.props and PropertySheet.props
automatically bridge it to MSBuild, install packages from vcpkg.json, and copy asmjit.dll to the output.
Run scripts\Get-WinRing0.ps1 once to place WinRing0x64.sys in the repository root (the
build copies it next to the executable automatically). Then open SelfModTamper.sln, select
x64 | Debug or x64 | Release, and build.
Using a manual AsmJit build (without vcpkg):
Set ASMJIT_DIR as described in Dependencies → AsmJit and restart Visual Studio.
PropertySheet.props picks up the variable automatically.
Only the x64 platform is supported. The static SMC in
TP.asmuses 64-bit registers and RIP-relative addressing; the dynamic generator inSelfModTamper.cpptargets x86-64 exclusively.The
.textsection must be writable for the static SMC variants. This is controlled via the linker flag/SECTION:.text,RWEalready present in the project.
REM Must be run from an elevated (Administrator) command prompt
REM CMake build output: build\Release\SelfModTamper.exe
REM .vcxproj build output: x64\Release\SelfModTamper.exe
build\Release\SelfModTamper.exeThe program:
- Pins itself to logical processor 0 and raises its priority to real-time.
- Loads/starts the WinRing0 driver and configures
IA32_PERFEVTSEL0to countMACHINE_CLEARS.SMCevents. - Iterates over five checksum variants × all seven timers for 10 000 trials each.
- Prints average / min / max times and total
MACHINE_CLEARS.SMCcount per variant. - Writes RDTSCP samples for the static SMC variant to
out.txt. - Unloads the driver cleanly on exit.
Sample output (Intel Core i7-9750H, Windows 10):
Manufacturer: GenuineIntel
DisplayModel: 0x9E DisplayFamily: 0x6
Architecture Performance Monitoring Version: 4 Number of Performance Counters: 4 PMC bit width: 48
The page size for this system is 4096 bytes.
Code segment base: 0x7FF7B4521000 size: 0x8200
Number of pages: 2 Unrolled loop size per page: 148 Intro bytes: 0x5F Code size: 0x1B
SelfModTamper/
├── CMakeLists.txt # Primary CMake build (recommended)
├── CMakePresets.json # Build presets — windows-x64 (Release/Debug)
├── vcpkg.json # vcpkg manifest — declares AsmJit dependency
├── scripts/
│ └── Get-WinRing0.ps1 # Downloads WinRing0x64.sys to repository root
├── SelfModTamper.sln # Visual Studio solution (legacy .vcxproj build)
├── SelfModTamper/
│ ├── SelfModTamper.cpp # Main source: timer classes, C reference implementations,
│ │ # dynamic SMC generator (AsmJit), benchmarking loop
│ ├── TP.asm # Static hand-written MASM SMC checksum (TPComp, TPCompLoop)
│ ├── Directory.Build.props # MSBuild early hook — sets VcpkgManifestRoot before vcpkg.props runs
│ ├── PropertySheet.props # MSBuild property sheet — bridges VCPKG_ROOT; Option C ASMJIT_DIR
│ └── SelfModTamper.vcxproj # Project file
├── .gitignore
├── .gitattributes
├── LICENSE
├── CITATION.cff
└── README.md
| Name | Source | Description |
|---|---|---|
C |
TPCompC in .cpp |
Pure C reference — no SMC |
SMC |
TPComp in TP.asm |
Static SMC single-loop, modifies its own opcode in-place |
C Loop |
TPCompCLoop in .cpp |
Pure C reference emulating loop-unrolled SMC |
SMC Loop |
TPCompLoop in TP.asm |
Static loop-unrolled SMC across two 4 KB pages |
Dyn SMC Loop |
gen_asm() in .cpp |
Dynamically generated loop-unrolled SMC (AsmJit, RWX heap) |
| Timer | SMC avg | Non-SMC avg | Ratio |
|---|---|---|---|
| RDTSCP (cycles) | 8 305 389 | 1 053 077 | 7.9× slower |
| Performance Counter | 32 045 | 4 064 | 7.9× slower |
| Timer | Dyn SMC avg | Non-SMC avg | Ratio |
|---|---|---|---|
| RDTSCP (cycles) | 467 286 | 42 300 840 | 90.5× faster |
| Performance Counter | 1 803 | 163 204 | 90.6× faster |
| Variant | Event count |
|---|---|
| C | 4 514 |
| SMC | 83 198 361 |
| C Loop | 20 986 |
| SMC Loop | 4 597 855 |
| Dyn SMC Loop | 4 492 871 |
The timing-based tamper-proofing scheme rests on several assumptions documented in the paper:
- A von Neumann (unified instruction/data memory) architecture is in use — not a Harvard architecture.
- Sufficiently many embedded checksum functions exist that an attacker cannot locate and patch all of them.
- No non-SMC variant exists that performs as fast or faster on the target hardware.
- Specialised hardware (e.g. FPGA with direct RAM access) would require a scheme with super-linear area complexity.
The checksum itself is not intended to be cryptographically secure in isolation; security of the scheme derives from timing scores and the dynamic/polymorphic nature of the code.
The scheme exploits the fact that several classic x86 arithmetic opcodes share an identical encoding except for 2 bits (bits 3–5):
| Instruction | Opcode | Bits [3:5] |
|---|---|---|
| ADD | 0x03 |
0 |
| OR | 0x0B |
1 |
| ADC | 0x13 |
2 |
| SBB | 0x1B |
3 |
| AND | 0x23 |
4 |
| SUB | 0x2B |
5 |
| XOR | 0x33 |
6 |
| CMP | 0x3B |
7 |
The SMC checksum cycles between ADC, SBB, XOR, and CMP (values 2, 3, 6, 7) by XOR-ing a single byte in the executing instruction stream.
If you use this code or reference the work, please cite the conference presentation:
@misc{morse2022tamper,
author = {Gregory Morse and Tam{\'a}s Kozsik},
title = {Tamper-Proofing with Self-Modifying Code},
howpublished = {Presented at CECC 2022, Smolenice, Slovakia},
month = jun,
year = {2022},
note = {Full manuscript unpublished; part of doctoral dissertation under review at E\"{o}tv\"{o}s Lor\'{a}nd University (ELTE)},
url = {https://github.com/GregoryMorse/SelfModTamper},
}See also CITATION.cff for the machine-readable citation metadata.
This project is licensed under the MIT License — see LICENSE for details.
- Gregory Morse — gregory.morse@live.com — Eötvös Loránd University (ELTE), Budapest, Hungary
- Tamás Kozsik — kto@elte.hu — Eötvös Loránd University (ELTE), Budapest, Hungary