Someone was silly enough to upload a working spectre (CVE-2017-5753) exploit for Linux (there is also a Windows one with symbols that I didn't look at.) on VirusTotal last month, so here is my quick Sunday afternoon lazy analysis.
The binary has its -h
option stripped, likely behind a #define
to avoid
detection, but some of its parameters are obvious, like specifying
what file to leak, or the kernel base address. The authors didn't check (or care)
that the logging function hasn't been entirely optimized out, leaving a bunch
of strings helping in the reversing process.
The exploit works in four stages:
- Find the superblock,
- Find the inode of the file to dump
- Find the corresponding page address
- Dumps the content of the file.
In the case of /etc/shadow
, the default option, the content of the
file is shoved in memory by running the following command in the
background: return system("echo \"whatever\n\" | su - 2> /dev/null")
.
In my lab, on a vulnerable Fedora, the exploit is successfully dumping /etc/shadow
in a couple of minutes.
Interestingly, there are checks to detect SMAP
and abort if it's present. I didn't manage to understand why the exploit was
failing in its presence.
edit: Andrew Cooper from Citrix was kind enough to reach out and solve the riddle:
The covert channel is presumably a userspace pointer which is accessed speculatively in kernel context, causing a cacheline fill, and recovered later by timing the reload. SMAP prevents supervisor code from accessing user memory operands outside of explicitly permitted areas. This is enough to prevent the cacheline fill (of a userspace pointer) and break the covert channel. A more sophisticated attack could use a supervisor pointer, e.g. the directmap mapping, or or one of a multitude of other covert channels to transmit the same data, which is a higher barrier, but definitely not impossible.
The crux of the exploit is at 0x4092f0
, using cpuid
as a serializing
instruction, rdtsc
for timing, and mfence
/lfence
as barrier, as
documented in the original paper. It's also using some tricks to minimize the amount of
readings, like type-specific functions, for example a kernel address has a
specific format.
Thanks to spender for confirming that the gadget used is likely get_user()
in the FIOASYNC ioctl
,
which was fixed in 2018
KASLR is bypassed when present either by
looking at /proc/kallsyms
when available to unprivileged users like it used
to be the case on Fedora until ~recently, or by using the generic
bypass from the prefetch side-channel by Gruss and al.
originating from a library called libkaslr
. Amusingly, this method is still
working on an up to date Linux, proving again that KASLR is useless against
local attacker.
For systems that don't have the /proc/kallsym
file accessible, the exploit
relies on hardcoded offsets, and while only Fedora, ArchLinux and Ubuntu are currently
supported, there are functions to check for Debian and CentOS. It's a bit
surprising to see hardcoded offsets in an exploit with arbitrary read in
2021.
Unsurprisingly, it had a 0 detection rate before I published this blogpost.
Attribution is trivial and left as an exercise to the reader.