bedevil: Dynamic Linker Patching
A deep technical analysis of the bedevil (bdvl) LD_PRELOAD rootkit's "dynamic linker patching" feature, which the threat actor Muddled Libra used against VMware vCenter servers. Instead of writing to /etc/ld.so.preload directly, bedevil binary-patches the system's ld.so dynamic linker files in place to point at a randomly-named preload path, evading common ld.so.preload/LD_PRELOAD detection checks. The post walks through bedevil's setup.py and ldpatch.h/patch.c source, then demonstrates investigation techniques — strace on the linker's initial access() call, rpm -V glibc/debsums integrity checks, and using statically linked find/strings/cp binaries to bypass the rootkit's hooked syscalls — and briefly notes the OrBit rootkit as a similar case.
What it is
The bedevil (bdvl) Linux rootkit's _ldpatch/ldpatch functions, which patch the on-disk dynamic linker (e.g. /lib64/ld-linux-x86-64.so.2) instead of relying on /etc/ld.so.preload. Controlled by PATCH_DYNAMIC_LINKER = True in setup.py (the default). Muddled Libra used bedevil against VMware vCenter (per Unit42, 2024).
How it works
_ldpatch(path, oldpreload, newpreload) reads a linker file into memory, scans byte-by-byte for the literal string /etc/ld.so.preload, and overwrites it in place with a replacement path. The replacement is generated per-host by 'PRELOAD_FILE':ut.randpath(18) in setup.py, so the preload path differs on every machine and cannot be threat-hunted as a fixed IOC.
The hard constraint worth remembering: the patch is in-place and must not change file size, so the function returns early if strlen(oldpreload) != strlen(newpreload). Because /etc/ld.so.preload is 18 characters, the randomized replacement is also exactly 18 characters (randpath(18)). The linker then reads that alternate file to load the syscall-hooking shared library.
Which files get patched is defined by the ldhomes array — the rootkit searches these directories for a valid ld.so to modify:
/lib
/lib32
/lib64
/libx32
/lib/x86_64-linux-gnu
/lib/i386-linux-gnu
/lib/arm-linux-gnueabihf./bdv uninstall reverses the patch, restoring the original /etc/ld.so.preload string.
What to do
Standard LD_PRELOAD checks (inspecting /etc/ld.so.preload contents or the environment variable) will miss this — the reference is baked into the linker binary itself, pointing at a random 18-char path. Investigate the linker binaries in the ldhomes paths directly: a clean linker still contains the literal string /etc/ld.so.preload; a patched one has that 18-byte region replaced with a random path. The post cites Tony Lambert's approach of matching against the expected string ("If you get a match, your linker probably hasn't been tampered with") — the fetched text is truncated here, so treat the exact detection command as unconfirmed and verify against the original post/tweet.
Caveats
The Investigation section is cut off mid-quote in the available text, so the concrete detection command is not fully captured. The 18-byte equality is the reliable anchor: any replacement path is exactly as long as /etc/ld.so.preload, which is what makes an in-place string comparison across the ldhomes linkers a viable check.
Organisations named here
- bdvl
- LD_PRELOAD
- OrBit
Technologies named here
- VMware vCenter servers
- strace
- glibc
- RPM-based Linux distributions
- xxd
- cat
- Debian-based systems
- find
- ld-linux-x86-64.so.2 / ld-2.17.so / ld-lsb-x86-64.so.3
- strings
- cp
- ldd
- grep
- file
- rpm -V glibc
- debsums
- Linux
Techniques named here
- Dynamic Linker Hijacking / Patching
- Random path generation for evasion
- Syscall hooking to conceal files
- Detection via strace on process execution
- Detection via static binary usage
- Detection via package integrity verification
- Detection via grep/strings on dynamic loader binaries
Threat actors named here
- Muddled Libra
- bedevil
- OrBit
Related in Blue Team & Detection
AI-Coded Malware | Analyzing Vibe-Coded AD Enumeration | Huntress
