
PoC CVE-2017-5123 - LPE - Contournement de SMEP/SMAP. Sans KASLR
PoC CVE-2017-5123 - LPE - Contournement de SMEP/SMAP. Sans KASLR
Dans ce petit article, j'analyserai une vulnérabilité du noyau qui nous permet d'obtenir les privilèges root.
Ce fichier est divisé en quatre parties :
Je tiens à souligner qu'il existe de bien meilleures façons d'exploiter cette CVE (en effet, ce n'est qu'un PoC pour apprendre le noyau, il ne peut pas être utilisé dans la nature) mais je pense que cette méthodologie peut être utile comme introduction à l'exploitation du noyau.
Cette vulnérabilité a été introduite dans 4c48abe91be0, nous devons donc compiler cette version du noyau.
Cela peut être un peu délicat car il s'agit d'une ancienne version et le code doit être patché.
J'ai créé un dépôt avec un code de noyau déjà patché et un fichier .config afin que vous puissiez cloner et compiler.
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
Notez que ce noyau sera compilé avec les pilotes virtio, vous pouvez donc utiliser un disque virtio pour partager des fichiers depuis/vers la VM.
Maintenant, nous allons créer le rootfs initial :
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
Ensuite, nous devons installer une distribution Linux de base, par exemple en utilisant pacstrap ou debootstrap.
sudo pacstrap /tmp/mount1 base base-devel vim
Enfin, nous pouvons modifier le système :
# 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 tout est en ordre, nous pouvons maintenant essayer notre système de test avec 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 description de la CVE indique qu'il existe une opération d'écriture non restreinte lors de l'appel système waitid.
Ouvrons kernel/exit.c et regardons le code :
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;
}
Cette fonction est assez simple : après quelques vérifications, il y a divers appels à unsafe_put_user(...) puis la fonction retourne.
La partie principale de cette fonction est composée de la fonction unsafe_put_user(...), alors rendons-nous là-bas (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)
Il y a un gros avertissement dans le commentaire : si vous voulez utiliser unsafe_put/get_user, vous devez d'abord appeler access_ok() et les entourer avec user_access_begin/end().
Si nous examinons le code précédent (waitid), nous voyons que access_ok() n'est jamais appelé, donc l'appel système viole cet avertissement.
Mais que sont ces macros ?
SMAP et SMEP sont deux fonctionnalités de sécurité introduites dans le noyau afin de rendre plus difficile l'écriture d'exploits. Il est à noter que ces fonctionnalités sont appliquées par le CPU.
SMEP empêche d'exécuter du code utilisateur lorsque le CPU est en mode superviseur ; SMAP, quant à lui, bloque l'accès lecture/écriture à la mémoire utilisateur.
Le noyau a besoin d'écrire/lire des données vers/depuis la mémoire utilisateur, et cela peut être accompli de deux manières :
copy_from_user) qui permettent de copier la mémoire dans l'espace noyau ;