
PoC CVE-2017-5123 - LPE - Umgehen von SMEP/SMAP. Kein KASLR
PoC CVE-2017-5123 - LPE - Umgehung von SMEP/SMAP. Kein KASLR
In dieser kleinen Abhandlung werde ich eine Kernel-Sicherheitslücke analysieren, die es uns ermöglicht, root-Rechte zu erlangen.
Diese Datei ist in vier Teile unterteilt:
Ich möchte darauf hinweisen, dass es viele bessere Möglichkeiten gibt, diese CVE auszunutzen (tatsächlich ist dies nur ein PoC zum Erlernen des Kernels und kann in freier Wildbahn nicht verwendet werden), aber ich denke, dass diese Methodik als Einführung in die Kernel-Ausnutzung nützlich sein kann.
Diese Schwachstelle wurde in 4c48abe91be0 eingeführt, daher müssen wir diese Kernel-Version bauen.
Das kann etwas knifflig sein, da es sich um eine alte Version handelt und der Code gepatcht werden sollte.
Ich habe ein Repository mit bereits gepatchtem Kernel-Code und einer .config-Datei erstellt, sodass Sie klonen und bauen können.
git clone https://github.com/c3r34lk1ll3r/kernel_mirror.git
cd kernel_mirror
git checkout origin/modified_v4.14
wget https://gist.githubusercontent.com/c3r34lk1ll3r/c9c34ae86140cc7a24d0d90141686ee8/raw/52431b577a71e3fe8f89d6ce355ce9c1c54c53b6/.config
make -j 8 --output-sync=recurse
Beachten Sie, dass dieser Kernel mit virtio-Treibern gebaut wird, sodass Sie virtio disk zum Teilen von Dateien zwischen Host und VM verwenden können.
Nun erstellen wir das initiale rootfs:
qemu-img create -f raw hda.raw 10G
# Formatieren der Festplatte zu ext4
mkfs.ext4 ./hda.raw
# Erstellen eines Mountpunkts für das Image
mkdir /tmp/mount1
# Einbinden der Festplatte
sudo mount -o loop ./hda.raw /tmp/mount1
Dann sollten wir eine grundlegende Linux-Distribution installieren, z.B. mit pacstrap oder debootstrap.
sudo pacstrap /tmp/mount1 base base-devel vim
Schließlich können wir das System anpassen:
# Hinzufügen eines 'test'-Benutzers
echo 'test:x:1000:1000::/home/test:/bin/bash' | sudo tee -a /tmp/mount1/etc/passwd
# ohne Passwort
echo 'test::14871::::::' | sudo tee -a /tmp/mount1/etc/shadow
# Wir können eine virtio-Festplatte mounten, um Dateien zwischen Host und Gast auszutauschen
echo '/transient /home/test/shared 9p trans=virtio,version=9p2000.L,rw,user,exec 0 0' | sudo tee -a /tmp/mount1/etc/fstab
sudo mkdir -p /tmp/mount1/home/test/shared
# Es ist nützlich, sudo-Berechtigung zu haben
echo '%wheel ALL=(ALL) NOPASSWD: ALL' | sudo tee -a /tmp/mount1/etc/sudoers
echo 'wheel:x:998:test' | sudo tee -a /tmp/mount1/etc/group
sudo chown -R 1000:1000 /tmp/mount1/home/test
sudo umount /tmp/mount1
Wenn alles in Ordnung ist, können wir nun unser Testsystem mit qemu ausprobieren:
qemu-system-x86_64 \
-kernel ./kernel_mirror/arch/x86_64/boot/bzImage \
-hda ./hda.raw \
-m 4G \
-cpu "Skylake-Client-IBRS,ss=on,vmx=on,hypervisor=on,tsc-adjust=on,clflushopt=on,umip=on,md-clear=on,stibp=on,arch-capabilities=on,ssbd=on,xsaves=on,pdpe1gb=on,ibpb=on,amd-ssbd=on,skip-l1dfl-vmentry=on,hle=off,rtm=off" \
-smp 4 \
-vga virtio \
-enable-kvm \
-nographic \
-machine type=q35,accel=kvm \
-virtfs "fsdriver=local,id=fs.1,path=./trans_fs,security_model=mapped,writeout=immediate,mount_tag=/transient" \
-append "root=/dev/sda rw noquiet nokaslr console=ttyS0 loglevel=5" \
-chardev "vc,id=vc.0,cols=1920,rows=1080" \
-net "user,hostfwd=tcp::10022-:22" \
-net "nic" \
-s
Die Beschreibung der CVE besagt, dass es während des Systemaufrufs waitid eine uneingeschränkte Schreiboperation gibt.
Öffnen wir kernel/exit.c und schauen uns den Code an:
SYSCALL_DEFINE5(waitid, int, which, pid_t, upid, struct siginfo __user *,
infop, int, options, struct rusage __user *, ru)
{
struct rusage r;
struct waitid_info info = {.status = 0};
long err = kernel_waitid(which, upid, &info, options, ru ? &r : NULL);
int signo = 0;
if (err > 0) {
signo = SIGCHLD;
err = 0;
if (ru && copy_to_user(ru, &r, sizeof(struct rusage)))
return -EFAULT;
}
if (!infop)
return err;
user_access_begin();
unsafe_put_user(signo, &infop->si_signo, Efault);
unsafe_put_user(0, &infop->si_errno, Efault);
unsafe_put_user(info.cause, &infop->si_code, Efault);
unsafe_put_user(info.pid, &infop->si_pid, Efault);
unsafe_put_user(info.uid, &infop->si_uid, Efault);
unsafe_put_user(info.status, &infop->si_status, Efault);
user_access_end();
return err;
Efault:
user_access_end();
return -EFAULT;
}
Diese Funktion ist recht einfach: Nach einigen Prüfungen gibt es verschiedene Aufrufe von unsafe_put_user(...) und die Funktion kehrt zurück.
Der Hauptteil dieser Funktion besteht aus der Funktion unsafe_put_user(...), also gehen wir dorthin (arch/x86/include/asm/uaccess.h):
/*
* The "unsafe" user accesses aren't really "unsafe", but the naming
* is a big fat warning: you have to not only do the access_ok()
* checking before using them, but you have to surround them with the
* user_access_begin/end() pair.
*/
#define user_access_begin() __uaccess_begin()
#define user_access_end() __uaccess_end()
#define unsafe_put_user(x, ptr, err_label) \
do { \
int __pu_err; \
__typeof__(*(ptr)) __pu_val = (x); \
__put_user_size(__pu_val, (ptr), sizeof(*(ptr)), __pu_err, -EFAULT); \
if (unlikely(__pu_err)) goto err_label; \
} while (0)
#define unsafe_get_user(x, ptr, err_label) \
do { \
int __gu_err; \
__inttype(*(ptr)) __gu_val; \
__get_user_size(__gu_val, (ptr), sizeof(*(ptr)), __gu_err, -EFAULT); \
(x) = (__force __typeof__(*(ptr)))__gu_val; \
if (unlikely(__gu_err)) goto err_label; \
} while (0)
Es gibt eine fette Warnung im Kommentar: Wenn Sie unsafe_put/get_user verwenden möchten, sollten Sie zuerst access_ok() aufrufen und sie mit user_access_begin/end() umschließen.
Wenn wir uns den vorherigen Code (waitid) ansehen, sehen wir, dass access_ok() nie aufgerufen wird, der Systemaufruf diese Warnung also verletzt.
Aber was sind diese Makros?
SMAP und SMEP sind zwei Sicherheitsfunktionen, die im Kernel eingeführt wurden, um das Schreiben von Exploits zu erschweren. Zu beachten ist, dass diese Funktionen von der CPU erzwungen werden.
SMEP verhindert die Ausführung von Userspace-Code, während sich die CPU im Supervisor-Modus befindet; SMAP blockiert den Lese-/Schreibzugriff auf den Benutzerspeicher.
Der Kernel muss Daten in den/vom Benutzerspeicher schreiben/lesen, und dies kann auf zwei Arten erfolgen:
copy_from_user), die das Kopieren des Speichers in den Kernelspace ermöglichen;