Beyond Cat-and-Mouse: From 2021 (a13xp0p0v) to 2026 (Singularity) — Exploitation Stage Is the Only Real Frontier
Beyond Cat-and-Mouse: From 2021 (a13xp0p0v) to 2026 (Singularity) — Exploitation Stage Is the Only Real Frontier
Singularity is currently one of the most highly engineered rootkits. It includes an implementation that bypasses LKRG, which has sparked quite a bit of discussion.
The current architecture of VED is based on LKRG, and theoretically suffers from the same fundamental weaknesses (although Singularity cannot be used directly — what is needed is adaptation rather than completely new techniques). Despite the heated discussions and the many techniques Singularity demonstrates, the core insight was already clear back when Alexander Popov (a13xp0p0v) bypassed LKRG using CVE-2021-26708: LKRG can very likely be bypassed in the post-exploitation phase, and there were (and still are) no truly effective countermeasures.
From this perspective, the problems exposed by Singularity do not go beyond what a13xp0p0v already showed — it simply represents better engineering practice, improving usability, stability, and stealth.
This is largely due to fundamental limitations in LKRG’s architecture:
- As a kernel module, once an exploit reaches kernel space, the attacker has the same ring-0 privileges as LKRG itself and can do virtually anything. This is an unavoidable weakness when using a kernel module as a security mechanism.
- LKRG’s design focuses primarily on the post-exploitation phase. Its good design makes data-pollution privilege escalation relatively easy to detect and hard to spread further. However, for control-flow hijacking attacks, it is trivial for an attacker to disarm LKRG early using various kernel techniques — especially since rootkit deployment usually starts with successfully loading a kernel module anyway.
The main countermeasures discussed by LKRG developers in response to the Singularity rootkit include:
- Checking and restricting insmod / rmmod / sysctl to prevent LKRG from being unloaded
- Hiding LKRG from the kernel module list (
lkrg.hide), obfuscating module name and function names - Adding
notraceto critical LKRG functions - Checking
register_kprobecalls and restricting other modules from using kprobe/ftrace - TZ (ring -1 / TrustZone)
This is only a brief summary.
Importantly, these discussions all assume that the rootkit is merely trying to evade checks performed by the security module, and that the security module’s own code remains intact and can reliably perform its checks. This is exactly why I say these approaches are not as “ultimate” as a13xp0p0v’s method: in his technique, LKRG’s hook points and timer handling functions are completely destroyed. What remains is merely an empty shell in the module list — all execution flow is dead. LKRG can no longer perform any checks, and crucially, this failure does not raise any alert because there is no longer any reliable, running code capable of performing self-checks or any other form of validation.
Except for pi3’s TZ (ring -1) suggestion, most of the measures listed above would not be effective against a13xp0p0v’s method — and implementing them via a module would actually be simpler. While module/function name obfuscation increases analysis difficulty somewhat, it still cannot fundamentally solve the problem when facing system module whitelisting or binary analysis.
Of course, we do not deny the value of these discussions. While rootkit implantation may not always occur via simple insmod, reusing existing toolchains is usually more stable from an engineering perspective — so adding these checks still makes practical sense. What we want to emphasize is that a13xp0p0v’s method already exposed the fundamental weakness of kernel-space security modules that cannot be ultimately solved.
Revisiting 2021: a13xp0p0v and CVE-2021-26708
Let’s recall the key techniques Alexander Popov used to bypass LKRG with CVE-2021-26708:
- Calling
mod->exit()to unload LKRG echo > /sys/kernel/debug/kprobes/enableto disable kprobes- Hijacking LKRG’s execution flow to patch its checking code and bypass detection
The third method is the inescapable curse of kernel security modules. It shows that LKRG’s check routines (whether passively triggered p_cmp_task or the active integrity timer) can be modified in advance — as long as it happens before the next p_check_integrity run. The security module remains completely unaware, because it lacks any reliable, guaranteed-to-execute code path capable of performing checks — including checks on itself.
Many of the techniques discussed in the Singularity thread (notrace, restricting kprobe/ftrace, etc.) were already achieved by a13xp0p0v at a lower level using text_poke. In the context of that exploit chain, even insmod checks could not intercept it. This approach demonstrates that post-exploitation security detection modules struggle to survive against customized attacks.
What can we do? There is still no ultimate solution
We must pay much more attention to the exploitation phase.
Since a13xp0p0v’s method was published, we have been very concerned about this issue. VED implemented several mitigation measures at the time and has continued to strengthen them. However — apart from exploitation-stage defenses — most other measures fall into the same category as the ones discussed above and cannot ultimately solve the problem.
Regarding exploitation defense, this is where VED and LKRG philosophies diverge significantly. We carefully studied multiple real attack cases and exploitation primitives (msg-msg, pipe-buffer, core-pattern, poison-obj, etc.) and implemented preventive measures against them (introduced in previous articles). These defenses take effect before privilege escalation occurs.
Although on the surface they appear narrower than LKRG’s broad post-exploitation checking surface, once a particular attack chain is blocked at the exploitation stage, the defense usually requires no further strategy changes — only continuous research into new cases to expand coverage. These mitigations can also be theoretically estimated and reasoned about.
VED also introduced more exploitation-stage wCFI checks (different from LKRG’s pCFI), targeting functions commonly used in ROP chains. These checks are very generic and significantly reduce the risk of rootkit implantation.
Feasible post-exploitation measures
Although post-exploitation checking appears broad, it easily turns into an endless cat-and-mouse game — especially when everything is open source and bypasses are quickly discovered (though it remains effective against attackers who do not know the bypasses). There is also a timing issue. For example, LKRG’s privilege-escalation detection operates at syscall granularity (with some schedule and other points). Deciding the right granularity is highly experience-dependent. We believe LKRG’s implementation in this regard is already excellent.
Since 2021, VED has added the following measures to make low-cost bypass harder (though we do not expect an ultimate solution):
- wCFI + permission checks on
disable_kprobe - wCFI on
native_write_cr4andtext_poke - wCFI and permission checks on
p_lkrg_deregister module_cleanupchecks- Shorter-period RO_GUARD timer to check kprobe status, etc.
- Increased use of inline functions to make hooking harder
Note that VED’s wCFI is implemented via binary scanning of allowed call targets — different from LKRG’s pCFI. For example, a rootkit calling text_poke might not trigger pCFI, but is more likely to be caught by wCFI. The idea of restricting kprobe/ftrace discussed in the LKRG issue can be implemented more precisely in VED through wCFI. We mainly deploy wCFI in the exploitation phase, with some coverage in post-exploitation, but it is not un-bypassable.
Similarly, we protect disable_kprobe, p_lkrg_deregister, mod->exit, etc. with wCFI + permission checks to prevent unloading or disabling of VED. The mod->exit case, for example, came from attack surface analysis — we previously thought p_lkrg_deregister was sufficient.
Because VED’s integrity timer (inherited from LKRG) has relatively high overhead, we designed shorter-period timers to check critical data that could be tampered with during exploitation (e.g. core_pattern). We also retain status checks on VED’s core kprobe points. Originally we hoped long- and short-term timers could mutually validate integrity, but after attack-defense iteration we concluded it cannot be fully solved — so we kept the kprobe status check, which triggers an alert on legitimate unload of VED. Again: this does not ultimately solve the problem.
To increase hijacking difficulty, we use inline functions as much as possible, spreading code across multiple kprobe handlers. This prevents single critical functions from being trivially hijacked and increases the steps and time required for a bypass. In post-exploitation scenarios, delaying until the next check arrives already has defensive value.
Post-exploitation imagination
The timer design here actually represents some of the post-rootkit checking ideas we experimented with in 2021. When we no longer expect the security module to run reliably, the next-best thing is at least knowing when it has been disabled.
UnhookingLinuxEdr countermeasures are among the few production-ready results so far. Without introducing ring -1, we currently have no more practical methods to share.
The idea of using TEE to protect VED originated from the self-sovereignty philosophy and self-custody hacking journey. We referenced pi3’s LKRG-TEE concept and Anton Rybakov’s approach, experimented with TA-to-kernel ECDH shared secret channels, progressively refreshed shared read-only memory to protect core data, and a generic measurement framework supporting TPM/TEE/TA.
This work is still experimental. The recent discussions have broadened our thinking, and we look forward to sharing more approaches in the future.