Machete
Thwarting Code-Reuse Attacks Through Temporal Permission Tightening in User-Space
Y. Çolakoğlu (TU Delft - Electrical Engineering, Mathematics and Computer Science)
G. Smaragdakis – Graduation committee member (TU Delft - Cyber Security)
A. Voulimeneas – Graduation committee member (TU Delft - Cyber Security)
V. Moutafis – Mentor (TU Delft - Cyber Security)
S.S. Chakraborty – Graduation committee member (TU Delft - Programming Languages)
More Info
expand_more
Other than for strictly personal use, it is not permitted to download, forward or distribute the text or part of it, without the consent of the author(s) and/or copyright holder(s), unless the work is under an open content license such as Creative Commons.
Abstract
Modern software ships with substantial unused code. Dynamic linking loads entire shared libraries when only a fraction is required, features accumulate over release cycles, and one-size-fits-all distribution models ship complete binaries regardless of deployment context. This bloat directly expands the attack surface available to adversaries: unused but mapped code provides gadgets for return-oriented programming (ROP) and jump-oriented programming (JOP) attacks. Existing defenses are partial. Address space layout randomisation is defeated by memory-disclosure vulnerabilities. Execute-only memory prevents reading code pages but does not reduce their volume. The W⊕X policy prevents code injection but not code reuse.
Software debloating removes unused code, but existing tools face trade-offs between source access, soundness, precision, and deployment requirements. Binary-level tools such as Razor operate only on the application binary and leave shared libraries fully mapped. Static-analysis tools such as Decker are conservative in their approximation and likewise skip library code. Kernel-level mechanisms can achieve strong isolation but require kernel modifications that limit deployment. No existing system combines temporal restriction, where different code is accessible at different stages of execution, with execute-only memory enforcement over the full dependency chain in user space.
We present Machete, a software debloating framework that derives temporal memory-access policies from execution traces and enforces them entirely in user space, without kernel modifications or source code access. Machete operates in three stages. A segfault-based profiler captures page-granularity access patterns for both single- and multi-threaded programs. A modified Blue-Fringe/EDSM learner infers a phase-structured finite-state machine from these traces, with tunable scoring coefficients that control the security/performance trade-off. An enforcement runtime then runs each phase in a separate operating-system process with its own page-table permissions and execute-only memory over shared physical memory, debloating the full dependency chain, including shared libraries.
We evaluate Machete against Razor and Decker on 11 shared targets and 2 auxiliary multi-threaded targets. Machete reduces executable pages by 86 to 95% from the original binary; even the worst-case phase exposes 2 to 5 times fewer pages than both Razor and Decker. ROP gadgets drop by 33 to 78% per phase, and all enforced variants have zero readable-executable application pages, preventing runtime gadget discovery. For CPU-bound workloads, enforcement overhead is below 4%. For server workloads, overhead ranges from 2.7% (memcached) to 66.6% (lighttpd), depending on phase-transition frequency, and a parameter sensitivity analysis confirms that the operator can navigate this trade-off through stable regions in the scoring-coefficient space. Machete is, to our knowledge, the first system to combine temporal debloating of executable pages with execute-only memory enforcement in user space.