Spectre exploits in the "wild"
Mon 01 March 2021 — download

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:

  1. Find the superblock,
  2. Find the inode of the file to dump
  3. Find the corresponding page address
  4. 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.