
PoC CVE-2017-5123 - LPE - Evadiendo SMEP/SMAP. Sin KASLR
PoC CVE-2017-5123 - LPE - Bypasando SMEP/SMAP. Sin KASLR
En este pequeño artículo, analizaré una vulnerabilidad del kernel que nos permite obtener privilegios de root.
Este archivo está dividido en cuatro partes:
Quiero señalar que hay muchas mejores formas de explotar este CVE (de hecho, esto es solo un PoC para aprender el kernel, no se puede usar en el mundo real), pero creo que esta metodología puede ser útil como introducción a la explotación del kernel.
Esta vulnerabilidad fue introducida en 4c48abe91be0, por lo que necesitamos compilar esa versión del kernel.
Esto puede ser un poco complicado porque es una versión antigua y el código debe estar parcheado.
Hice un repositorio con código de kernel ya parcheado y un archivo .config para que puedas clonar y compilar.
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
Ten en cuenta que este kernel se compilará con controladores virtio, por lo que puedes usar disco virtio para compartir archivos desde/hacia la VM.
Ahora, crearemos el rootfs inicial:
qemu-img create -f raw hda.raw 10G
# Format the disk to ext4
mkfs.ext4 ./hda.raw
# Make a mountpoint for the image
mkdir /tmp/mount1
# Mount the disk
sudo mount -o loop ./hda.raw /tmp/mount1
Luego, deberíamos instalar una distribución básica de Linux, por ejemplo usando pacstrap o debootstrap.
sudo pacstrap /tmp/mount1 base base-devel vim
Finalmente, podemos modificar el sistema:
# Add a 'test' user
echo 'test:x:1000:1000::/home/test:/bin/bash' | sudo tee -a /tmp/mount1/etc/passwd
# without password
echo 'test::14871::::::' | sudo tee -a /tmp/mount1/etc/shadow
# we can mount a virtio disk in order to share files between host and guest
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
# It is usefull to have sudo permission
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
Si todo está en orden, ya podemos probar nuestro sistema de pruebas con qemu:
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
La descripción del CVE dice que hay una operación de escritura sin restricciones durante la llamada al sistema waitid.
Abramos kernel/exit.c y veamos el código:
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;
}
Esta función es bastante sencilla: después de algunas comprobaciones, hay varias llamadas a unsafe_put_user(...) y la función retorna.
La parte principal de esta función está compuesta por la función unsafe_put_user(...), así que vayamos allí (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)
Hay una gran advertencia en el comentario: si quieres usar unsafe_put/get_user, primero debes llamar a access_ok() y rodearlos con user_access_begin/end().
Si observamos el código anterior (waitid), podemos ver que access_ok() nunca se llama, por lo que la llamada al sistema viola esta advertencia.
Pero, ¿qué son esas macros?
SMAP y SMEP son dos características de seguridad introducidas en el kernel para hacer más difícil escribir exploits. Cabe señalar que estas características son aplicadas por la CPU.
SMEP impide ejecutar código de espacio de usuario mientras la CPU está en modo supervisor; SMAP, en cambio, bloquea el acceso de lectura/escritura a la memoria de usuario.
El kernel necesita escribir/leer datos desde/hacia la memoria de usuario y esto se puede lograr de dos maneras:
copy_from_user) que permiten copiar la memoria en el espacio del kernel;Como podemos ver en la definición de unsafe_put_user, esta función solo copia el valor de x en la memoria apuntada por ptr (y salta a err_label si hubo un error). Acabamos de decir que el kernel no puede acceder al espacio de usuario debido a SMAP, y es por eso que estas funciones deben estar envueltas entre user_access_begin/end().