xnu内核对象_像植物一样打破xnu内核

xnu内核对象

PPL’s goal is to prevent an attacker from modifying a process’s executable code or page tables, even after obtaining kernel read/write/execute privileges. It does this by leveraging APRR to create something of a “kernel inside the kernel” that protects page tables. During normal kernel execution, page tables and page table metadata are read-only, and code that modifies page tables is non-executable; the only way for the kernel to modify page tables is to enter PPL by calling a “PPL routine”, which is analogous to a syscall from XNU into PPL. This limits the entry points into the kernel code that can modify page tables to just those PPL routines.

PPL的目标是即使在获得内核读取/写入/执行特权之后,也可以防止攻击者修改进程的可执行代码或页表。 它通过利用APRR来创建某种“内核内部的内核”来保护页表,从而做到这一点。 在正常的内核执行期间,页表和页表元数据是只读的,并且修改页表的代码是不可执行的。 内核修改页表的唯一方法是通过调用“ PPL例程”进入PPL,这类似于从XNU到PPL的系统调用。 这将限制可以修改页表的内核代码的入口点限制为仅那些PPL例程。

I considered several ideas to bypass PPL using the one-byte technique’s physical mapping primitive, including mapping page tables directly, mapping a DART to allow modifying physical memory from a coprocessor, and mapping the I/O addresses used to control clock gating to power down certain components of the system. Unfortunately, none of these ideas panned out.

我考虑了几种使用单字节技术的物理映射原语绕过PPL的想法,包括直接映射页表,映射DART以允许从协处理器修改物理内存,以及映射用于控制时钟门控以掉电的I / O地址。系统的某些组件。 不幸的是,这些想法都没有得到解决。

However, it’s not the Project Zero way to leave any mitigation unbroken. So, having exhausted my search for design flaws, I returned to the ever-faithful technique of memory corruption. Sure enough, decompiling a few PPL functions in IDA was sufficient to find some memory corruption.

但是,这不是使任何缓解措施保持不间断的零项目方式。 因此,在穷尽了对设计缺陷的搜索之后,我回到了忠实的内存破坏技术。 果然,在IDA中反编译一些PPL函数足以发现某些内存损坏。

Image for post
Some memory corruption in pmap_remove_options_internal(). Using a kernel function calling primitive, both va_start and size are controlled.
pmap_remove_options_internal()中的某些内存损坏。 使用调用原始函数的内核函数,可以控制va_start和size。

The function pmap_remove_options_internal() is a PPL routine, one of the “PPL syscalls” from the XNU kernel to the even more privileged PPL. It is called by invoking pmap_remove_options() in XNU, which validates arguments and then calls pmap_remove_options_internal() in PPL. Its purpose is to unmap the supplied virtual address range from the physical memory map (pmap) of a process.

函数pmap_remove_options_internal()是一个PPL例程,它是从XNU内核到特权更高的PPL的“ PPL系统调用”之一。 通过在XNU中调用pmap_remove_options()来调用它,该方法将验证参数,然后在PPL中调用pmap_remove_options_internal() 。 其目的是从进程的物理内存映射(pmap)取消映射提供的虚拟地址范围。

MARK_AS_PMAP_TEXT static int
pmap_remove_options_internal(
pmap_t pmap,
vm_map_address_t start,
vm_map_address_t end,
int options)

The actual work of removing the translation table entries (TTEs) that map the supplied virtual address range is done by calling pmap_remove_range_options(), which takes pointers to the beginning and end of the TTE range to remove from the level 3 (leaf) translation table.

删除映射映射提供的虚拟地址范围的转换表条目(TTE)的实际工作是通过调用pmap_remove_range_options()来完成的,该操作将指向TTE范围起点和终点的指针从级别3(叶)转换表中删除。

static int
pmap_remove_range_options(
pmap_t pmap,
pt_entry_t *bpte, // The first L3 TTE to remove
pt_entry_t *epte, // The end of the TTEs
uint32_t *rmv_cnt,
int options)

Unfortunately, when pmap_remove_options_internal() calls pmap_remove_range_options(), it seems to assume that the supplied virtual address range will not cross an L3 translation table boundary, because if it does then the calculated TTE range will span out-of-bounds memory:

不幸的是,当pmap_remove_options_internal()调用pmap_remove_range_options()时 ,似乎假定所提供的虚拟地址范围不会跨越L3转换表边界,因为如果这样做,则计算出的TTE范围将跨越越界内存:

remove_count = pmap_remove_range_options(
pmap,
&l3_table[(va_start >> 14) & 0x7FF],
(u64 *)((char *)&l3_table[(va_start >> 14)]
+ ((size >> 11) & 0x1FFFFFFFFFFFF8LL)),
&rmv_spte,
options);

This means that if we have an arbitrary kernel function calling primitive, we can invoke the PPL-entering wrapper function directly and get pmap_remove_options_internal() called with an improper virtual address range, which makes pmap_remove_range_options() try to remove “TTEs” read from out-of-bounds memory while in PPL mode. And since the removed TTEs are zeroed out, this means that we can corrupt PPL-protected memory.

这意味着,如果我们有一个任意的调用原始函数的内核函数,则可以直接调用PPL进入包装器函数,并获得带有不正确虚拟地址范围的pmap_remove_options_internal()调用,这会使pmap_remove_range_options()尝试删除从中读出的“ TTE”在PPL模式下,内存越界。 而且由于删除的TTE被清零,这意味着我们可以破坏PPL保护的内存。

Image for post

But zeroing out-of-bounds TTEs would be a rather annoying primitive to try and leverage for a PPL bypass. Much of the data we’d like to corrupt has probably already been allocated far away from our page tables, and PPL isn’t a large enough code base that we’re guaranteed to find something interesting we can do just by zeroing memory. And that’s to say nothing of the accounting in PPL that would probably detect an attempt to unmap non-existent TTEs!

但是将越界的TTE归零将是尝试并利用PPL旁路的一个相当烦人的原语。 我们想破坏的许多数据可能已经分配到了远离页表的地方,而且PPL的代码库不够大,我们无法保证找到一些有趣的事情,只需将内存清零即可。 这还没说PPL中的会计处理,它很可能会检测到试图取消映射不存在的TTE的企图!

So instead I chose to focus on a side effect of this out-of-bounds processing: improper TLB invalidation.

因此,我选择专注于这种越界处理的副作用:不正确的TLB无效。

Later on in pmap_remove_options_internal(), after the TTEs have been removed, the translation lookaside buffer (TLB) needs to be invalidated in order to ensure that the process cannot continue to access the unmapped pages through stale TLB entries.

稍后在pmap_remove_options_internal()中,在删除TTE之后,需要使转换后备缓冲区 (TLB)无效,以确保进程无法继续通过陈旧的TLB条目访问未映射的页面。

flush_mmu_tlb_region_asid_async(va_start, size, pmap);

This TLB flush occurs on the supplied virtual address range, not the removed TTEs. Thus, there could be a disagreement between the TLB entries invalidated and the L3 TTEs removed if the out-of-bounds TTEs were from a separate region of the process’s address space, leaving stale TLB entries for those out-of-bounds TTEs.

TLB刷新发生在提供的虚拟地址范围上,而不是在删除的TTE上。 因此,如果超出范围的TTE来自进程地址空间的单独区域,则无效的TLB条目和删除的L3 TTE之间可能存在分歧,从而为那些超出范围的TTE留下陈旧的TLB条目。

Image for post

A stale TLB entry would allow a process to continue accessing the physical page after that page has been unmapped and potentially reused for page tables. So if we had a stale TLB entry for an L3 translation table, then we could insert L3 TTEs to map arbitrary PPL-protected pages as writable.

过时的TLB条目将允许进程在对该页面取消映射并可能重新用于页面表之后继续访问该物理页面。 因此,如果我们有一个用于L3转换表的过时的TLB条目,则可以插入L3 TTE来将任意受PPL保护的页面映射为可写。

That’s pretty much exactly how the PPL bypass works:

这就是PPL旁路的工作原理:

  1. Call the kernel function cpm_allocate() to allocate 2 pages of contiguous physical memory called A and B.

    调用内核函数cpm_allocate()分配2页连续的物理内存,称为A和B。
  2. Call pmap_mark_page_as_ppl_page() to insert pages A and B at the head of the ppl_page_list so they can be reused for page tables.

    调用pmap_mark_page_as_ppl_page()ppl_page_list的开头插入页面A和B,以便可以将它们重新用于页面表。

  3. Fault in pages for virtual addresses P and Q so that A and B are allocated as L3 TTs for mapping P and Q, respectively. P and Q are discontiguous but have TTEs that are contiguous.

    虚拟地址P和Q的页面出现故障,因此A和B分别分配为L3 TT,用于映射P和Q。 P和Q不连续,但TTE连续。
  4. Start a spinner thread bound to a CPU core that reads from page Q in a loop to keep the TLB entry alive.

    启动绑定到CPU内核的微调器线程,该线程在循环中从Q页读取,以保持TLB条目有效。
  5. Call pmap_remove_options() to remove 2 pages starting from virtual address P (which does not include Q). The vulnerability means that TTEs for both P and Q are removed, but only the TLB entry for P is invalidated.

    调用pmap_remove_options()从虚拟地址P(不包括Q)开始删除2页。 该漏洞意味着删除了P和Q的TTE,但只有P的TLB条目无效。
  6. Call pmap_mark_page_as_ppl_page() to insert page Q at the head of the ppl_page_list so it can be reused for page tables.

    调用pmap_mark_page_as_ppl_page()将页面Q插入ppl_page_list的开头,以便可以将其重新用于页表。

  7. Fault in a page for virtual address R so that page Q is allocated as an L3 TT for R, even while we continue to have a stale TLB entry for Q.

    虚拟地址R的页面中存在错误,因此即使我们继续为Q提供旧的TLB条目,也将页面Q分配为R的L3 TT。
  8. Using the stale TLB entry, write to page Q to insert an L3 TTE which maps Q itself as writable.

    使用陈旧的TLB条目,写到Q页以插入一个L3 TTE,该LTETE将Q自身映射为可写的。
Image for post

This bug demonstrates a common problem when creating a security boundary where none existed before. It’s easy for code to make subtle assumptions about the security model (such as where argument validation occurs or what functionality is exposed vs. private) that no longer hold true under the new model. I wouldn’t be surprised to see more bugs along this line in PPL.

当创建以前没有安全边界的安全边界时,此错误演示了一个常见问题。 代码很容易对安全模型做出微妙的假设(例如,参数验证发生的位置或公开的功能与私有的相比)在新模型下不再成立。 如果在PPL中看到更多错误,我不会感到惊讶。

Overall, though, I came away from this exercise impressed with the design of PPL. My biggest criticism is that the value-add proposition of PPL is still not yet clear to me: What real-world attacks does PPL mitigate? Is it simply laying the groundwork for more sophisticated and powerful mitigations to come? Whatever the answer may be, I still prefer having it. Kudos to Apple for an interesting and well-thought-out mitigation.

总的来说,我对PPL的设计印象深刻。 我最大的批评是,对于我而言,PPL的增值主张尚不明确:PPL可以缓解哪些实际攻击? 它是否只是为进一步完善和强有力的缓解措施打下基础? 无论答案是什么,我仍然更愿意拥有它。 致敬苹果公司,提出有趣且经过深思熟虑的缓解措施。

翻译自: https://medium.com/@ovennell/breaking-the-xnu-kernels-kernel-like-a-plant-ec982764aa22

xnu内核对象

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值