Skip to content
KitploitKITPLOIT
工具博客
提交
工具博客
提交

黑客、渗透测试和网络安全工具,武装您的安全武器库!

Kitploit 是一个黑客、网络安全和渗透测试工具的目录。发现最新的项目更新,查找漏洞、分析系统、自动化测试并加强你的安全。

··订阅源·联系·隐私·© 2026 Kitploit

工具目录

分类

查看所有分类
Loading categories
Pixel_GPU_Exploit — 适用于 Pixel7/8 Pro 的 Android 14 内核漏洞利用 | Kitploit
工具/GitHubGitHub/0x36/pixel_gpu_exploit
Android安全权限提升内存取证漏洞分析漏洞利用学习与教育二进制利用
GitHub0x36/pixel_gpu_exploit

Pixel_GPU_Exploit

适用于 Pixel7/8 Pro 的 Android 14 内核漏洞利用

查看仓库
5578842年前Kitploit 审核通过

最受欢迎

查看全部 →

发现我们社区最常用的工具。

探索所有工具

浏览我们的工具集合

查看所有工具 →
分享

Mali GPU 内核 LPE

本文深入分析了 Mali GPU 中的两个内核漏洞,这些漏洞可从默认应用沙箱中触达,由我独立发现并报告给 Google。文中包含一个可实现任意内核读写(r/w)能力的内核利用程序。该利用程序可禁用 SELinux,并在运行以下 Android 14 版本的 Google Pixel 7 和 8 Pro 机型上提权至 root:

  • 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) 提供)

漏洞

该利用程序利用了以下两个漏洞:gpu_pixel_handle_buffer_liveness_update_ioctl ioctl 命令中因不完整补丁导致的整数溢出,以及时间线流(timeline stream)消息缓冲区中的信息泄露。

因不正确的整数溢出修复导致 gpu_pixel_handle_buffer_liveness_update_ioctl() 中的缓冲区下溢

Google 已在此提交中修复了 gpu_pixel_handle_buffer_liveness_update_ioctl ioctl 命令中的一个整数溢出问题。起初,当我报告此问题时,我以为该漏洞是由之前所述补丁中的问题引起的。在审查报告后,我意识到我对该漏洞的分析并不准确。尽管我最初假设补丁不完整,但它确实有效解决了并防止了计算中的下溢。这让我怀疑该更改并未应用到生产构建中。然而,尽管我可以触发计算中的下溢,却无法引发溢出。这表明该 ioctl 命令已被部分修复,尽管不是通过上述补丁实现的。查看 IDA 后发现,生产版本中搭载了另一个不完整的补丁,而该补丁不存在于 mali GPU 内核模块的任何 git 分支中。

该漏洞最早在最新 Android 版本中被发现,并于 2023 年 11 月 19 日报告。Google 后来告知我,他们已在内部识别出该漏洞,并在 12 月 Android 安全公告中将其分配为 CVE-2023-48409,将其标记为重复问题。 尽管我能够确认该漏洞在我报告前数月就已在内部被识别(基于约 8 月 30 日的提交日期),但仍存在困惑。具体来说,最新设备在 10 月和 11 月的安全补丁级别(SPL)仍然受此漏洞影响,这很奇怪——我尚未调查这些版本之前的版本。因此,我无法最终确定这是否确实是重复问题,也无法确定相关补丁是否确实在我提交之前就已计划在 12 月发布,还是在解决此漏洞时存在疏忽。

无论如何,这个漏洞之所以强大,原因如下:

  • 缓冲区 info.live_ranges 完全由用户控制。
  • 溢出值为用户可控输入,因此我们可以使计算发生溢出,从而让 info.live_ranges 指针位于 buff 内核地址起始之前的任意偏移处。
  • 分配大小同样为用户可控输入,这使我们能够从任意通用 slab 分配器中请求内存分配。

此漏洞与我于 2022 年在 iOS 15 内核中发现并利用的 DeCxt::RasterizeScaleBiasData() 缓冲区下溢漏洞 有相似之处。

时间线流消息缓冲区中的内核指针泄露

Mali GPU 实现了一个自定义的 timeline stream,用于收集信息、序列化,然后按照特定格式将其写入环形缓冲区。用户可以调用 kbase_api_tlstream_acquire ioctl 命令获取文件描述符,从而读取该环形缓冲区。消息格式如下:

  • 一个数据包头

  • 一个消息 ID

  • 一个序列化的消息缓冲区,其具体内容取决于消息 ID。 例如,__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:~
该概念验证漏洞利用通过监视消息 ID `KBASE_TL_KBASE_NEW_KCPUQUEUE` 来泄露 `kbase_kcpu_command_queue` 对象地址;该消息由 `kbasep_kcpu_queue_new` 函数在新分配 kcpu 队列对象时发出。

谷歌告知我,该漏洞于 2023 年 3 月被报告,并已在其安全公告中被分配为 [CVE-2023-26083](https://source.android.com/docs/security/bulletin/2023-07-01)。尽管如此,我仍然能够在搭载 10 月和 11 月安全补丁级别(SPL)的最新 Pixel 设备上复现该问题,这表明修复措施未正确应用或根本没有应用。随后,谷歌在 12 月安全更新公告中迅速解决了该问题,但未提供致谢,并后来告知我该问题被视为重复报告。然而,将此问题标记为重复的理由仍然值得怀疑。

## 漏洞利用
---
因此,我有两个有趣的漏洞。第一个漏洞提供了强大的能力,可以修改位于已分配 ~buff~ 地址之前的任意 16 字节对齐内核地址的内容。第二个漏洞则提供了有关内核内存中对象潜在位置的提示。

### 关于 buffer_count 和 live_ranges_count 值的说明
由于可以完全控制 `buffer_count` 和 `live_ranges_count` 字段,我能够灵活地选择目标 slab 以及想要写入的精确偏移。然而,由于多种约束和因素,选择 `buffer_count` 和 `live_ranges_count` 的值需要仔细考虑:
- 这两个值是相互关联的,只有绕过所有新引入的检查后,溢出才会发生。
- 负偏移必须为 16 字节对齐这一要求,限制了向任意选定位置写入的能力。不过,这通常不会构成重大障碍。
- 选择较大的偏移会导致大量数据被写入可能并非预期目标的系统内存区域。例如,如果分配大小溢出到 `0x3004`,`live_ranges` 指针将相对于 `buff` 对象分配的空间设置为 `-0x4000` 字节。然后,`copy_from_user` 函数将根据 `update->live_ranges_count` 乘以 4 的计算结果写入 `0x7004` 字节。因此,该操作会导致用户控制的数据覆盖 `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 缓存分配器来为我的分配缓冲区提供服务吗?这并不正确,因为原因又出在 copy_from_user 上!这是由 CONFIG_HARDENED_USERCOPY 缓解机制导致的。它禁止指定一个与目标缓冲区(此处为堆对象)所对应的 slab 缓存大小不匹配的大小。它会判断缓冲区的页面是否为 slab 页;如果是,则获取匹配的 kmem_cache->size,并判断用户提供的大小是否不会超过该值;否则,内核就会因大小不匹配而直接崩溃。所以,换句话说,我无法攻击属于通用分配器的对象,但我仍然可以攻击具有较大尺寸的对象(即由页分配器直接服务的对象)。

我首先想到的是使用 pipe_buffer 技术,这是一种非常优雅的获取任意读写原语的技术。我不会深入讨论该技术,但建议读者阅读 Interrupt Labs 的这篇精彩博客。在构造管道对象时,pipe_buffer 对象最初创建为一个包含 16 个元素的数组;但是,可以使用 fcntl(F_SETPIPE_SZ) 调整数组大小。因此,pipe_buffer 数组的分配可以被调整,使其能够由页分配器提供服务,从而成为完美的攻击目标对象。 在选择 pipe_buffer 对象作为候选目标后,实现内核 r/w 的下一步是利用下溢漏洞覆写其内容,这将允许我读写任意内存位置,只要将这些位置的页覆写到 pipe_buffer->page 字段。 由于该漏洞允许我写入任意数据,我可以控制整个 pipe_buffer 的内容,包括其 page 字段。要做到这一点,我需要在易受攻击的 kbuff 对象之前分配 pipe_buffer 数组,并且它们必须彼此相邻。

让 pipe_buffer 和 buff 对象相邻放置

我向内核内存喷射了大量 kbase_kcpu_command_queue 对象,随后喷射了一堆 pipe_buffer 数组。 由于 pipe_max_size 的限制,我不能仅靠 pipe_buffer 数组作为主要的喷射源。因此,我决定先用 kbase_kcpu_command_queue 对象进行喷射。选择 kbase_kcpu_command_queue 对象有两个原因:其分配大小为 0x38C8,因此由页分配器处理;而且我可以利用内核信息泄漏漏洞确定性地获得其内核地址,使其既适合作为喷射对象,也适合作为攻击目标(我们将在下一节看到)。

如前所述,我使用 fcntl(F_SETPIPE_SZ) 增大了 pipe_buffer 数组的分配大小,以便由页分配器提供服务。更具体地说,我选择将分配大小设为一个 ==0x4000 字节 (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`,否则管道会增加 head/tail 计数器,从而导致使用新的 `pipe_buffer` 对象,并失去对伪造管道缓冲区的控制。
此外,`flags` 必须为 `PIPE_BUF_FLAG_CAN_MERGE`,这样后续的 `pipe_write` 调用不会盲目地递增 head 计数器并使用下一个管道缓冲区,而是首先检查当前 `pipe_buffer` 中是否有足够的空间来容纳写入请求,如果有,它就会从 `len` 字段存储的值开始,将数据简单地追加到同一个管道缓冲区中。
为了避免在 `pipe_buf_confirm` 处使设备崩溃(`pipe_buf_confirm` 由 `pipe_write` 和 `pipe_read` 调用),`ops` 指针也必须是一个有效的内核地址,并且 `ops->confirm` 字段设置为 _NULL_。我可以直接使用已泄露的 `kbase_kcpu_command_queue` 对象中某个为 NULL 且在任何情况下都不会改变的偏移量。

### 为下溢选择最佳偏移值
虽然 `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` 对象!**
用 `pipe_buffer` 替换 `kbase_kcpu_command_queue`,我们就能操控合法的管道缓冲区,而无需经常更新 `.page` 字段。不过,我仍然需要处理 `.len` 和 `.offset` 字段。

### 修改 pipe_buffer→len/offset 字段

正如我之前提到的,执行管道读/写操作会更新 `.len` 和 `.offset` 字段,使得对同一页面的后续读/写操作无法使用,即使是在两个不同的管道上执行也不行。这里还有一个技巧:**有一种技术可以在完全不触碰 `.len/.offset` 字段的情况下读写数据!**。而这可以通过让 `pipe_read/write` 中的 `copy_page_from_iter` 和 `copy_page_to_iter` 调用触发 fault(缺页错误)来实现!是的,就像 `copy_to/from_user` 一样,`copy_page_to/from_iter` 会将数据从/向用户空间复制,这些用户空间数据通过 `iov_iter` 结构传递,而这个复制过程是可以被 fault 的。

继续前面的例子,如果我们希望向某个地址写入 8 字节数据,所提供的用户空间缓冲区大小必须是 8 字节,后面跟着一段未映射或不可读的内存区域,然后向 `write` 系统调用传入 `9` 作为大小参数,以表示我们想要写入的数据量。该操作会先写入 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 个字节不可读,然后只需声明我们要读取 9 字节,数据就会被复制到用户缓冲区,而不会改变 .offset 字段。 因此,我们能够对任意内核内存地址执行无限制的读/写操作,而无需反复经过 spray 过程。

获取 root 权限

既然我已经拥有了强大的任意读/写原语,我便按照 Interrupt Labs 博客文章中概述的技术,遍历了 VMEMMAP_START 数组中的所有 struct page,以确定内核文本起始地址。然后我意识到,在 Android 十一月安全更新 中 init_task 被置空了,所以我改用 kthreadd_task。有了 kthreadd_task 内核地址,我就可以遍历 task->tasks 列表,获取自身 current 任务的内核地址,然后将 cred 结构清零,从而获得 root 权限。

后来我意识到,扫描所有页面地址是不必要的,因为我已经从一个 pipe_buffer 对象中获得了 anon_pipe_buf_ops 内核文本地址。有了这些信息,我就能推导出内核文本基地址,从而有效绕过 KASLR。

禁用 SELinux

该漏洞还会禁用 SELinux。有了内核文本基地址,我只需找到 selinux_state 全局结构的位置,然后将 .enforcing 值清零。

概念验证

报告随附的概念验证已在运行 Android 14、搭载 10 月和 11 月 ASB 的 Pixel 7 和 8 Pro 设备上进行了测试,成功率接近 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应用来运行该漏洞利用程序,只需将此目录嵌入其中,并确保通过在cmake文件中添加`-w -Wno-c++11-narrowing`来禁用无用的C++警告。

### 演示```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
下载工具