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

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

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

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

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

Категории

Все категории
Loading categories
Инструменты/GitHubGitHub/0x36/pixel_gpu_exploit
Безопасность AndroidПовышение привилегийКриминалистика памятиАнализ уязвимостейЭксплуатацияОбучение и ОбразованиеЭксплуатация Бинарных Файлов
GitHub0x36/pixel_gpu_exploit

Pixel_GPU_Exploit

Эксплойт ядра Android 14 для Pixel7/8 Pro

Репозиторий
5578842 лет назадПроверено Kitploit

Популярное

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

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

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

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

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

Mali GPU Kernel LPE

Эта статья предоставляет углубленный анализ двух уязвимостей ядра в Mali GPU, доступных из стандартной песочницы приложений, которые я самостоятельно выявил и сообщил в Google. Она включает эксплойт ядра, достигающий произвольного чтения/записи в ядро. Впоследствии он отключает SELinux и повышает привилегии до root на Google Pixel 7 и 8 Pro с указанными версиями Android 14:

  • Pixel 8 Pro: google/husky/husky:14/UD1A.231105.004/11010374:user/release-keys
  • Pixel 7 Pro: google/cheetah/cheetah:14/UP1A.231105.003/11010452:user/release-keys
  • Pixel 7 Pro: google/cheetah/cheetah:14/UP1A.231005.007/10754064:user/release-keys
  • Pixel 7: google/panther/panther:14/UP1A.231105.003/11010452:user/release-keys (от m4b4 (Marcel))

Уязвимости

Этот эксплойт использует две уязвимости: целочисленное переполнение из-за неполного исправления в ioctl-команде gpu_pixel_handle_buffer_liveness_update_ioctl, и утечку информации в буферах сообщений потока временной шкалы.

Недостаток буфера в gpu_pixel_handle_buffer_liveness_update_ioctl() из-за некорректного исправления целочисленного переполнения

Google устранил целочисленное переполнение в ioctl-команде gpu_pixel_handle_buffer_liveness_update_ioctl в этом коммите. Сначала, когда я сообщил об этой проблеме, я думал, что ошибка вызвана проблемой в упомянутом патче. После анализа отчета я осознал, что мой анализ уязвимости был неточным. Несмотря на мое первоначальное предположение о неполноте патча, он эффективно устраняет и предотвращает недостаток в вычислениях. Это заставило меня заподозрить, что изменение не было применено в производственных сборках. Однако, хотя я могу вызвать недостаток в вычислениях, переполнение вызвать невозможно. Это предполагает, что ioctl-команда была частично исправлена, хотя и не с помощью указанного выше патча. Анализ в IDA показал, что в производственных релизах поставляется другой неполный патч, отсутствующий в любой ветке git модуля ядра Mali GPU.

Эта уязвимость была впервые обнаружена в последней версии Android и сообщена 19 ноября 2023 года. Google позже уведомил меня, что они уже внутренне идентифицировали её и присвоили CVE-2023-48409 в декабрьском бюллетене безопасности Android, пометив как дубликат. Хотя я смог проверить, что ошибка была внутренне идентифицирована за месяцы до моего отчета (исходя из даты коммита около 30 августа), остается неясность. В частности, странно, что уровни исправлений безопасности (SPL) за октябрь и ноябрь самых последних устройств все еще были подвержены этой уязвимости — я не исследовал версии, предшествующие этим. Поэтому я не могу окончательно определить, была ли это действительно дублирующаяся проблема и было ли соответствующее исправление запланировано на декабрь до моей подачи, или же было упущение в устранении этой уязвимости.

В любом случае, что делает эту ошибку мощной, так это следующее:

  • Буфер info.live_ranges полностью контролируется пользователем.
  • Переполняемые значения — это ввод, контролируемый пользователем, поэтому мы можем вызвать переполнение вычисления, так что указатель info.live_ranges может находиться на произвольном смещении до начала адреса ядра buff.
  • Размер выделения также контролируется пользователем, что дает возможность запросить выделение памяти из любого универсального slab-аллокатора.

Эта уязвимость схожа с уязвимостью недостатка буфера DeCxt::RasterizeScaleBiasData(), которую я нашел и эксплуатировал в ядре iOS 15 в 2022 году.

Утечка указателей ядра в буферах сообщений потока временной шкалы

GPU Mali реализует пользовательский поток временной шкалы, предназначенный для сбора информации, её сериализации и последующей записи в кольцевой буфер в определенном формате. Пользователи могут вызвать ioctl-команду kbase_api_tlstream_acquire, чтобы получить файловый дескриптор, позволяющий читать из этого кольцевого буфера. Формат сообщений следующий:

  • Заголовок пакета

  • Идентификатор сообщения

  • Сериализованный буфер сообщения, конкретное содержимое которого зависит от идентификатора сообщения. Например, функция __kbase_tlstream_tl_kbase_kcpuqueue_enqueue_fence_wait сериализует указатели ядра kbase_kcpu_command_queue и dma_fence в буфер сообщения, что приводит к утечке указателей ядра в пользовательский процесс.```c void __kbase_tlstream_tl_kbase_kcpuqueue_enqueue_fence_wait( struct kbase_tlstream *stream, const void *kcpu_queue, const void *fence ) { const u32 msg_id = KBASE_TL_KBASE_KCPUQUEUE_ENQUEUE_FENCE_WAIT; const size_t msg_size = sizeof(msg_id) + sizeof(u64) + sizeof(kcpu_queue) + sizeof(fence) ; char *buffer; unsigned long acq_flags; size_t pos = 0;

    buffer = kbase_tlstream_msgbuf_acquire(stream, msg_size, &acq_flags);

    pos = kbasep_serialize_bytes(buffer, pos, &msg_id, sizeof(msg_id)); pos = kbasep_serialize_timestamp(buffer, pos); pos = kbasep_serialize_bytes(buffer, pos, &kcpu_queue, sizeof(kcpu_queue)); pos = kbasep_serialize_bytes(buffer, pos, &fence, sizeof(fence));

    kbase_tlstream_msgbuf_release(stream, acq_flags); }

root@kitploit:~
Доказательство концепции эксплуатации утечки адреса объекта `kbase_kcpu_command_queue` путем мониторинга идентификатора сообщения `KBASE_TL_KBASE_NEW_KCPUQUEUE`, который отправляется функцией `kbasep_kcpu_queue_new` при каждом выделении нового объекта очереди kcpu.

Google сообщил мне, что уязвимость была зарегистрирована в марте 2023 года и получила идентификатор [CVE-2023-26083](https://source.android.com/docs/security/bulletin/2023-07-01) в их бюллетене безопасности. Тем не менее, мне удалось воспроизвести проблему на новейших устройствах Pixel, поставляемых с уровнями исправлений безопасности (SPL) за октябрь и ноябрь, что указывает на то, что исправление не было применено правильно или вообще не было применено. Впоследствии Google быстро устранил проблему в декабрьском бюллетене обновлений безопасности, не указав автора, а позже сообщил мне, что проблема считается дубликатом. Однако обоснование, почему эту проблему пометили как дубликат, остается спорным.

## Эксплуатация
---
Итак, у меня есть две интересные уязвимости. Первая предоставляет мощную возможность изменять содержимое любого 16-байтового выровненного адреса ядра, который находится перед выделенным ~buff~ адресом. Вторая уязвимость дает подсказки о возможных местоположениях объектов в памяти ядра.

### Примечания к значениям buffer_count и live_ranges_count

Имея полный контроль над полями `buffer_count` и `live_ranges_count`, я могу гибко выбирать целевой slab и точное смещение, которое собираюсь записать. Однако выбор значений для `buffer_count` и `live_ranges_count` требует тщательного рассмотрения из-за нескольких ограничений и факторов:
- Оба значения связаны, и переполнение произойдет только в том случае, если все вновь введенные проверки будут обойдены.
- Требование, чтобы отрицательное смещение было выровнено по 16 байтам, ограничивает возможность записи в произвольное место. Однако это обычно не является существенным препятствием.
- Выбор большего смещения приводит к записи большого объема данных в области памяти, которые могут не быть целевыми. Например, если размер выделения переполняется до `0x3004`, указатель `live_ranges` будет установлен на `-0x4000` байт от выделенного пространства объекта `buff`. Функция `copy_from_user` затем запишет `0x7004` байт, исходя из расчета `update->live_ranges_count` умноженного на 4. Следовательно, эта операция приведет к перезаписи пользовательскими данными области памяти между указателем `live_ranges` и выделением `buff`. Поэтому необходимо тщательно убедиться, что никакие критически важные системные объекты в этом диапазоне не будут случайно перезаписаны. Учитывая, что операция включает вызов `copy_from_user`, можно подумать о том, чтобы вызвать `EFAULT` путем намеренного снятия отображения нежелательной области памяти после исходного буфера пользователя, чтобы предотвратить запись данных в чувствительные места. Однако этот подход неэффективен, потому что если функция `raw_copy_from_user` завершится ошибкой, она обнулит оставшиеся байты в целевом буфере ядра. Такое поведение реализовано для того, чтобы в случае частичного копирования из-за ошибки остальная часть буфера ядра не содержала неинициализированных данных.```c
static inline __must_check unsigned long
_copy_from_user(void *to, const void __user *from, unsigned long n)
{
	unsigned long res = n;
	might_fault();
	if (!should_fail_usercopy() && likely(access_ok(from, n))) {
		instrument_copy_from_user(to, from, n);
		res = raw_copy_from_user(to, from, n);
	}
	if (unlikely(res))
		memset(to + (n - res), 0, res);
	return res;
}

Учитывая это, нам нужно тщательно выбрать объект для перезаписи и данные для записи.

Выбор правильного объекта для перезаписи

Поскольку я застрял с этой неудачной проверкой, моя стратегия — найти объект, который при обнулении не приведет к нежелательным последствиям. Но прежде чем перейти к этому, есть еще одна проблема, с которой нужно разобраться. Помните, в прошлой части я сказал, что могу выбрать любой размер выделения и, следовательно, любой аллокатор общего назначения slab cache для обслуживания моего буфера выделения? Это неверно, потому что опять из-за copy_from_user! Это связано с механизмом защиты CONFIG_HARDENED_USERCOPY. Он запрещает указывать размер, который не соответствует размеру соответствующего slab cache, к которому относится буфер назначения ядра (в данном случае объекта кучи). Он определяет, является ли страница буфера slab-страницей, и если да, то получает соответствующий kmem_cache->size и проверяет, не превысит ли предоставленный пользователем размер его; в противном случае ядро просто падает из-за несоответствия размера. Иными словами, я не могу нацеливаться на объекты, принадлежащие аллокатору общего назначения, НО я все еще могу нацеливаться на объекты больших размеров (т. е. те, которые обслуживаются непосредственно аллокатором страниц).

Первая мысль, которая пришла в голову, — использовать технику pipe_buffer, которая является очень элегантным способом получения примитивов произвольного чтения/записи. Я не буду вдаваться в подробности этой техники, но читателям рекомендуется прочитать этот замечательный блог от Interrupt Labs. При создании объекта pipe объект pipe_buffer изначально создается в массиве из 16 элементов; однако размер массива можно изменить с помощью fcntl(F_SETPIPE_SZ). Таким образом, выделение массива pipe_buffer может быть настроено так, чтобы он обслуживался аллокатором страниц, что делает его идеальным целевым объектом для атаки. После выбора объекта pipe_buffer в качестве цели, следующий шаг к достижению чтения/записи ядра — перезаписать его содержимое с помощью уязвимости underflow, что позволит мне читать/записывать из/в любую область памяти, страница которой перезаписывает поле pipe_buffer->page. Поскольку уязвимость позволяет мне записывать произвольные данные, я могу контролировать все содержимое pipe_buffer, включая его поле page, и для этого мне нужно выделить массив pipe_buffer до уязвимого объекта kbuff, и они должны находиться рядом друг с другом.

Размещение объектов pipe_buffer и buff рядом

Я засорил память ядра множеством объектов kbase_kcpu_command_queue, а затем последовал за ними массивом pipe_buffer. Я не могу использовать только массивы pipe_buffer в качестве основного источника для распыления из-за ограничения, налагаемого pipe_max_size. Поэтому я решил начать распыление с объекта kbase_kcpu_command_queue. Выбор объекта kbase_kcpu_command_queue был обусловлен двумя причинами: его размер выделения равен 0x38C8, поэтому он обрабатывается аллокатором страниц, и я могу детерминированно получить его адрес в ядре, используя информационную уязвимость утечки ядра, что делает его хорошим объектом для распыления, а также хорошей целью (как мы увидим в следующем разделе).

Как упоминалось ранее, я использовал fcntl(F_SETPIPE_SZ) для увеличения размера выделения массива pipe_buffer, чтобы он мог обслуживаться аллокатором страниц. Если быть точнее, я выбрал размер выделения равным ==0x4000 bytes (4 * PAGE_SIZE)==, чтобы соответствовать выделениям kbase_kcpu_command_queue.

Получение адреса struct page

Для правильного использования pipe_buffer требуется адрес страницы. Возможность идентифицировать адрес ядра объекта kbase_kcpu_command_queue, который я могу намеренно создавать и уничтожать, делает его хорошим кандидатом для использования, а найти соответствующий struct page можно с помощью virt_to_page.

Содержимое для записи в pipe_buffer

Итак, объект pipe_buffer выглядит следующим образом:```c struct pipe_buffer { struct page *page; unsigned int offset, len; const struct pipe_buf_operations *ops; unsigned int flags; unsigned long private; };

root@kitploit:~
Как уже упоминалось, поле `page` должно содержать корректный адрес страницы. Поля `offset` и `len` не должны превышать `PAGE_SIZE`, иначе канал (pipe) увеличит счётчики head/tail, что приведёт к использованию нового объекта `pipe_buffer` и потере контроля над поддельным буфером канала.  
Кроме того, `flags` должно быть `PIPE_BUF_FLAG_CAN_MERGE`, чтобы при последующих вызовах `pipe_write` вместо слепого увеличения счётчика head и использования следующего `pipe_buffer` сначала проверялось, есть ли место в текущем `pipe_buffer`, которое вместит запрос на запись, и если да, то данные просто дописывались в тот же буфер, начиная со значения, хранящегося в поле `len`.  

Чтобы избежать краха устройства в `pipe_buf_confirm`, который вызывается `pipe_write` и `pipe_read`, указатель `ops` также должен быть корректным адресом ядра с полем `ops->confirm`, установленным в _NULL_. Я могу просто использовать смещение внутри утёкшего объекта `kbase_kcpu_command_queue`, которое равно NULL и не изменится ни при каких обстоятельствах.

### Выбор оптимального значения смещения для переполнения вниз (underflow)

Хотя размеры выделения для `buff`, `kbase_kcpu_command_queue` и `pipe_buffer` составляют ~0x4000~ байт, я решил выполнить переполнение вниз буфера на **0x8000** байт. Почему?

Давайте кратко рассмотрим, как обновляются `pipe_buffers` во время операций чтения и записи. Предположим, мы можем сформировать `pipe_buffer` следующим образом:```c
struct pipe_buffer {
	.page = virt_to_page(addr),
	.offset =  0,
	.len = 0x40,
	.ops = kcpu_addr + 0x50,
	.flags = PIPE_BUF_FLAG_CAN_MERGE,
	unsigned long private = 0
};

Хотя ошибка даёт возможность произвольно управлять содержимым этого объекта, она делает это только один раз, потому что объект с переполнением вниз освобождается сразу после завершения вызова ioctl. Это на самом деле создаёт проблему, так как мне нужно вручную обновлять объект pipe_buffer, чтобы сделать его снова пригодным для использования, поскольку каждая операция чтения/записи в канале:

  • Поле .page не обновляется; оно остаётся тем же, и когда буфер пуст, он освобождается, чего я не хочу, потому что поле .ops установлено некорректно.
  • Поскольку pipe_buffer обновляет поле .offset при операции чтения, я не могу прочитать ту же область памяти снова.
  • Данные, записанные в pipe_buffer, будут добавлены в буфер, начиная со значения .len (при условии, что установлен флаг PIPE_BUF_FLAG_CAN_MERGE), и .len обновляется соответственно. То есть мы не можем записать данные в точный адрес дважды.

В результате, если я не обновлю pipe_buffer после каждой операции чтения или записи, я не смогу одновременно читать и записывать из/в один и тот же канал. Вот почему переполнение вниз на 0x8000 байт гораздо практичнее, потому что вместо перезаписи одного pipe_buffer, я перезапишу два различных экземпляра pipe_buffer двух различных объектов каналов: один будет предназначен для чтения, а другой — для записи.```c #define PIPE_BUF_FLAG_CAN_MERGE 0x10 /* can merge buffers */

pipe_read = (struct pipe_buffer *)( ptr); pipe_read->page = virt_to_page(ta->kcpu_kaddr); pipe_read->offset = 0; pipe_read->len = 0xfff; pipe_read->ops = (const void *)(ta->kcpu_kaddr + 0x50); pipe_read->flags = PIPE_BUF_FLAG_CAN_MERGE; pipe_read->private = 0;

pipe_write = (struct pipe_buffer )( ptr + 0x4000); pipe_write->page = virt_to_page(ta->kcpu_kaddr); pipe_write->offset = 0; pipe_write->len = 0; / This is the starting position of the pipe_write */ pipe_write->ops = (const void *)(ta->kcpu_kaddr + 0x50); pipe_write->flags = PIPE_BUF_FLAG_CAN_MERGE; pipe_write->private = 0;

root@kitploit:~
`pipe_read` — это фиктивный буфер канала, который будет использоваться для чтения данных с целевой страницы, начиная с `.offset = 0` и до `0xfff` байт, тогда как `pipe_write` — это фиктивный `pipe_buffer`, который будет использоваться для записи данных, начиная с `.len = 0` и до `0xfff` байт. 
Также очень важно еще раз отметить, что запись более `PAGE_SIZE` байт приведет к увеличению счетчика head в канале, что приведет к использованию свежего вновь выделенного `pipe_buffer` и потере контроля над нашим фиктивным `pipe_write`. С другой стороны, опустошение (чтение 0xfff данных из) буфера `fake_read` заставляет ядро освободить фактическую страницу вызовом `ops→release`, что вызывает крах ядра, так как у меня пока нет адреса текста ядра. 
Хотя мне удалось разделить операции чтения и записи в канале так, что выполнение записи в одном конце канала не мешает другому буферу канала и наоборот, я все еще не решил основную проблему: как надежно обновлять буфер канала? Очевидным ответом было просто повторять процесс заливки снова и снова после каждого вызова чтения или записи в канал. И это не имеет смысла, потому что это существенно повлияло бы на надежность эксплойта. В следующем разделе я разделю цель на две подцели: для начала я сосредоточусь только на поле `.page`, а затем на полях `.len/.offset`. 

### Изменение поля pipe_buffer→page
К моему удивлению, мне не нужно и не требуется обновлять поле `.page` вообще, потому что я могу перезаписать `pipe_buffer→page`, чтобы он указывал на адрес страницы утекшего `kbase_kcpu_command_queue`. Следовательно, **все, что мне нужно сделать, это освободить объект `kbase_kcpu_command_queue` и перекрыть его новым объектом `pipe_buffer`. Ага! Теперь у меня есть `pipe_buffer→page`, указывающий на легитимный объект `pipe_buffer`!**
Замена `kbase_kcpu_command_queue` на `pipe_buffer` дает нам возможность манипулировать легитимным буфером канала без необходимости регулярно обновлять поле `.page`. Однако мне все еще нужно разобраться с полями `.len` и `.offset`.

### Изменение полей pipe_buffer→len/offset
Как я упоминал ранее, выполнение чтения/записи в канале обновляет поля `.len` и `.offset`, делая последующие операции чтения/записи на той же странице непригодными, даже если они выполняются через два разных канала. Вот еще один трюк: **существует техника чтения/записи данных без касания полей `.len/.offset`!**. И это возможно путем вызывания ошибки (`fault`) в вызовах `copy_page_from_iter` и `copy_page_to_iter` в `pipe_read/write`! Да, так же, как и с `copy_to/from_user`, `copy_page_to/from_iter` копирует данные из/в пользовательское пространство, переданное через структуру `iov_iter`, и в ней можно вызвать ошибку. 

Продолжая предыдущий пример, если мы хотим записать 8 байт данных по некоторому адресу, размер предоставленного буфера пользовательского пространства должен быть 8, за которым следует неотображенная или недоступная для чтения область памяти, а затем передать `9` в качестве аргумента размера системному вызову `write`, указывая количество данных, которые мы хотим записать. Эта операция запишет 8 байт и завершится ошибкой на _девятом_, так как столкнется с неотображенной/нечитаемой областью памяти. В результате данные фактически записаны в целевой буфер ядра, а поле `.len` не было изменено. Функция ядра `pipe_write` просто вернется без обновления поля `buf->len`.```c
		if ((buf->flags & PIPE_BUF_FLAG_CAN_MERGE) &&
		    offset + chars <= PAGE_SIZE) {
			ret = pipe_buf_confirm(pipe, buf);
			if (ret)
				goto out;

			ret = copy_page_from_iter(buf->page, offset, chars, from);
			if (unlikely(ret < chars)) {
				ret = -EFAULT;
				goto out;
			}

			buf->len += ret;
			if (!iov_iter_count(from))
				goto out;
		}

То же самое верно и для операций чтения; если мы хотим прочитать 8 байт, сделаем девятый байт буфера недоступным для чтения и просто заявим, что хотим прочитать 9 байт — данные будут скопированы в пользовательский буфер без изменения поля .offset.
В результате мы можем выполнять неограниченные операции чтения/записи по любому адресу памяти ядра, без необходимости повторно проходить процесс распыления.

Получение root-прав

Теперь, имея мощный примитив произвольного чтения/записи, я просто просмотрел все struct page в массиве VMEMMAP_START, чтобы определить начальный адрес текста ядра, используя метод, описанный в блоге Interrupt Labs. Затем я обнаружил, что init_task обнуляется в Android November Security Updates, поэтому вместо него я использовал kthreadd_task. Имея адрес ядра kthreadd_task, я смог пройти по списку task->tasks и получить адрес ядра своей собственной задачи current, после чего обнулить структуру cred для получения root-привилегий.

Позже я понял, что сканировать все адреса страниц необязательно, поскольку у меня уже был адрес текста ядра anon_pipe_buf_ops из объекта pipe_buffer. С этой информацией я мог вычислить базовый адрес текста ядра, фактически обойдя KASLR.

Отключение SELinux

Эксплойт также отключает SELinux: с базовым адресом текста ядра мне нужно лишь найти местоположение глобальной структуры selinux_state, а затем обнулить поле .enforcing.

Proof of Concept

Эксплойт PoC, приложенный к отчёту, был протестирован на устройствах Pixel 7 и 8 Pro под управлением Android 14 с ASB октября и ноября, достигнув почти 100% успеха. Также важно отметить, что эксплойт не будет работать «из коробки» на других устройствах из-за использования некоторых жёстко закодированных смещений. Чтобы добавить поддержку нового устройства, необходимо указать следующее:

  • смещение kthreadd_task от базового адреса ядра.
  • смещение selinux_state от базового адреса ядра.
  • смещения полей task_struct->cred, task_struct->pid и task_struct->tasks.
  • смещение anon_pipe_buf_ops от базового адреса ядра.

Компиляция

Чтобы скомпилировать эксплойт как отдельный бинарный файл, используйте следующую команду, затем используйте adb shell для его запуска:```sh $ aarch64-linux-androidXX-clang++ -static-libstdc++ -w -Wno-c++11-narrowing -DUSE_STANDALONE -o poc poc.cpp -llog $ adb push poc /data/local/tmp/ $ adb shell /data/local/tmp/poc

root@kitploit:~
Вы также можете запустить эксплойт через приложение Android Studio, встроив этот каталог вместе с ним, и убедитесь, что отключили бесполезные предупреждения C++, добавив `-w -Wno-c++11-narrowing` в файл cmake.

### Демо```shell
$ adb logcat  |grep -i EXPLOIT
11-28 16:04:12.500  7989  7989 E EXPLOIT : [+] Target device: 'google/husky/husky:14/UD1A.231105.004/11010374:user/release-keys' 0xa9027bfdd10203ff 0xa90467faa9036ffc
11-28 16:04:15.563  7989  7989 E EXPLOIT : [+] Got the kcpu_id (0) kernel address = 0xffffff8901390000  from context (0x0)
11-28 16:04:18.441  7989  7989 E EXPLOIT : [+] Got the kcpu_id (255) kernel address = 0xffffff89b0bf8000  from context (0xff)
11-28 16:04:18.442  7989  7989 E EXPLOIT : [+] Found corrupted pipe with size 0xfff
11-28 16:04:18.442  7989  7989 E EXPLOIT : [+] SUCCESS! we have a fake pipe_buffer (0)!
11-28 16:04:18.444  7989  7989 E EXPLOIT : 10 00 39 01 89 FF FF FF  10 00 39 01 89 FF FF FF  | ..9.......9.....
11-28 16:04:18.444  7989  7989 E EXPLOIT : 00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  | ................
11-28 16:04:18.444  7989  7989 E EXPLOIT : 00 B0 CD 12 C0 FF FF FF  00 00 00 00 00 00 00 00  | ................
11-28 16:04:18.444  7989  7989 E EXPLOIT : 00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  | ................
11-28 16:04:18.445  7989  7989 E EXPLOIT : [+] Freeing kcpu_id = 0 (0xffffff8901390000)
11-28 16:04:18.446  7989  7989 E EXPLOIT : [+] Allocating 61 pipes with 256 slots
11-28 16:04:18.462  7989  7989 E EXPLOIT : [+] Successfully overlapped the kcpuqueue object with a pipe buffer
11-28 16:04:18.463  7989  7989 E EXPLOIT : 40 AB BA 26 FE FF FF FF  00 00 00 00 30 00 00 00  | @..&........0...
11-28 16:04:18.463  7989  7989 E EXPLOIT : 70 37 8D F1 DA FF FF FF  10 00 00 00 00 00 00 00  | p7..............
11-28 16:04:18.463  7989  7989 E EXPLOIT : 00 00 00 00 00 00 00 00                           | ........
11-28 16:04:18.463  7989  7989 E EXPLOIT : [+] pipe_buffer {.page = 0xfffffffe26baab40, .offset = 0x0, .len = 0x30, ops = 0xffffffdaf18d3770}
11-28 16:04:18.463  7989  7989 E EXPLOIT : [+] kernel base = 0xffffffdaf0010000, kthreadd_task = 0xffffff8002da3780 selinux_state = 0xffffffdaf28a3168
11-28 16:04:20.097  7989  7989 E EXPLOIT : [+] Found our own task struct 0xffffff88416c5c80
11-28 16:04:20.097  7989  7989 E EXPLOIT : [+] Successfully got root: getuid() = 0 getgid() = 0
11-28 16:04:20.097  7989  7989 E EXPLOIT : [+] Successfully disabled SELinux
11-28 16:04:20.102  7989  7989 E EXPLOIT : [+] Cleanup  ... OK
Скачать инструмент